Go最全Google Jetpack 新组件 CameraX 介绍与实践(3),2024年最新来一份全面的面试宝典练练手

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

.setTargetAspectRatio(aspectRatio)
// 旋转
.setTargetRotation(rotation)
// 分辨率
.setTargetResolution(resolution)
// 前后摄像头
.setLensFacing(lensFacing)
.build()

// 创建 Preview 对象
val preview = Preview(previewConfig)
// 设置监听
preview.setOnPreviewOutputUpdateListener { previewOutput ->
// PreviewOutput 会返回一个 SurfaceTexture
cameraTextureView.surfaceTexture = previewOutput.surfaceTexture
}

return preview
}

通过建造者模式创建 Preview 对象,并且一定要给 Preview 对象设置 OnPreviewOutputUpdateListener 接口回调。

相机预览的图像流是通过 SurfaceTexture 来返回的,而在项目例子中,是通过把 TextureView 的 SurfaceTexture 替换成 CameraX 返回的 SurfaceTexture,这样实现了 TextureView 控件显示 Camera 预览内容。

另外,还需要考虑到设备的选择方向,当设备横屏变为竖屏了,TextureView 也要相应的做旋转。

preview.setOnPreviewOutputUpdateListener { previewOutput ->
cameraTextureView.surfaceTexture = previewOutput.surfaceTexture

// Compute the center of preview (TextureView)
val centerX = cameraTextureView.width.toFloat() / 2
val centerY = cameraTextureView.height.toFloat() / 2

// Correct preview output to account for display rotation
val rotationDegrees = when (cameraTextureView.display.rotation) {
Surface.ROTATION_0 -> 0
Surface.ROTATION_90 -> 90
Surface.ROTATION_180 -> 180
Surface.ROTATION_270 -> 270
else -> return@setOnPreviewOutputUpdateListener
}

val matrix = Matrix()
matrix.postRotate(-rotationDegrees.toFloat(), centerX, centerY)

// Finally, apply transformations to TextureView
cameraTextureView.setTransform(matrix)
}

TextureView 旋转的设置同样在 OnPreviewOutputUpdateListener 接口中去完成。

图像分析

bindToLifecycle 方法中,imageAnalyzer 参数并不是必需的。

ImageAnalysis 可以帮助我们做一些图像质量的分析,需要我们去实现 ImageAnalysis.Analyzer 接口的 analyze 方法。

fun buildImageAnalysisUseCase(): ImageAnalysis {
// 分析器配置 Config 的建造者
val analysisConfig = ImageAnalysisConfig.Builder()
// 宽高比例
.setTargetAspectRatio(aspectRatio)
// 旋转
.setTargetRotation(rotation)
// 分辨率
.setTargetResolution(resolution)
// 图像渲染模式
.setImageReaderMode(readerMode)
// 图像队列深度
.setImageQueueDepth(queueDepth)
// 设置回调的线程
.setCallbackHandler(handler)
.build()

// 创建分析器 ImageAnalysis 对象
val analysis = ImageAnalysis(analysisConfig)

// setAnalyzer 传入实现了 analyze 接口的类
analysis.setAnalyzer { image, rotationDegrees ->
// 可以得到的一些图像信息,参见 ImageProxy 类相关方法
val rect = image.cropRect
val format = image.format
val width = image.width
val height = image.height
val planes = image.planes
}

return analysis
}

在图像分析器的相关配置中,有个 ImageReaderModeImageQueueDepth 的设置。

ImageQueueDepth 会指定相机管线中图像的个数,提高 ImageQueueDepth 的数量会对相机的性能和内存的使用造成影响

其中,ImageReaderMode 有两种模式:

  • ACQUIRE_LATEST_IMAGE
  • 该模式下,获得图像队列中最新的图片,并且会清空队列已有的旧的图像。
  • ACQUIRE_NEXT_IMAGE
  • 该模式下,获得下一张图像。

在图像分析的 analyze 方法中,能通过 ImageProxy 类拿到一些图像信息,并基于这些信息做分析。

拍摄

拍摄同样有一个 Config 参数构建者类,而且设定的参数和预览相差不大,也是图像宽高比例、旋转方向、分辨率,除此之外还有闪光灯等配置项。

fun buildImageCaptureUseCase(): ImageCapture {
val captureConfig = ImageCaptureConfig.Builder()
.setTargetAspectRatio(aspectRatio)
.setTargetRotation(rotation)
.setTargetResolution(resolution)
.setFlashMode(flashMode)
// 拍摄模式
.setCaptureMode(captureMode)
.build()

// 创建 ImageCapture 对象
val capture = ImageCapture(captureConfig)
cameraCaptureImageButton.setOnClickListener {
// Create temporary file
val fileName = System.currentTimeMillis().toString()
val fileFormat = “.jpg”
val imageFile = createTempFile(fileName, fileFormat)

// Store captured image in the temporary file
capture.takePicture(imageFile, object : ImageCapture.OnImageSavedListener {
override fun onImageSaved(file: File) {
// You may display the image for example using its path file.absolutePath
}

override fun onError(useCaseError: ImageCapture.UseCaseError, message: String, cause: Throwable?) {
// Display error message
}
})
}

return capture
}

在图像拍摄的相关配置中,也有个 CaptureMode 的设置。

它有两种选项:

  • MIN_LATENCY
  • 该模式下,拍摄速度会相对快一点,但图像质量会打折扣
  • MAX_QUALITY
  • 该模式下,拍摄速度会慢一点,但图像质量好

OpenGL 渲染

以上是关于 CameraX 的简单应用方面的内容,更关心的是如何用 CameraX 去做 OpenGL 渲染实现美颜。滤镜等效果。

还记得在图像预览 Preview 的 setOnPreviewOutputUpdateListener 方法中,会返回一个 SurfaceTexture ,相机的图像流就是通过它返回的。

那么要实现 OpenGL 线程的渲染,首先就要基于 EGL 去创建 OpenGL 绘制环境,然后利用 SurfaceTexture 的 attachToGLContext 方法,将 SurfaceTexture 添加到 OpenGL 线程去。

attachToGLContext 的参数是一个纹理 ID ,这个纹理就必须是 OES 类型的纹理。

然后再把这纹理 ID 绘制到 OpenGL 对应的 Surface 上,这可以看成是两个不同的线程在允许,一个 Camera 预览线程,一个 OpenGL 绘制线程。

如果你不是很理解的话,建议还是看看上面提供的代码地址:

github.com/glumes/came…

也可以关注我的微信公众号 【纸上浅谈】,里面有一些关于 OpenGL 学习和实践的文章~~~

CameraX 的拓展

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

究,那么很难做到真正的技术提升。**

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 8
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于Jetpack Compose 组件androidx.compose.material3.Button点击和长按同时监听代码示例,可以参考以下示例代码: ``` import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapAndLongPress import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.example.myapp.viewmodel.CounterViewModel import kotlinx.coroutines.launch @ExperimentalFoundationApi @Composable fun ButtonClickListener() { val scaffoldState = rememberScaffoldState() var counterViewModel = viewModel<CounterViewModel>() Scaffold( scaffoldState = scaffoldState, modifier = Modifier.fillMaxSize(), content = { Column( modifier = Modifier .padding(top = 10.dp, start = 16.dp, end = 16.dp) .fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { Text(text = "点击或长按按钮可触发计数") Spacer(modifier = Modifier.height(16.dp)) MyButton( onClick = { counterViewModel.incrementCount() }, onLongClick = { counterViewModel.decrementCount() } ) Spacer(modifier = Modifier.height(16.dp)) Text( text = "当前计数:${counterViewModel.count}", style = MaterialTheme.typography.h4 ) } }, bottomBar = { BottomAppBar(cutoutShape = RoundedCornerShape(topStart = 16.dp)) { IconButton(onClick = { }) { Icon(Icons.Default.Save, contentDescription = "Save") } Spacer(Modifier.weight(1f, true)) IconButton(onClick = { }) { Icon(Icons.Default.Share, contentDescription = "Share") } } } ) } @ExperimentalFoundationApi @Composable fun MyButton(onClick: () -> Unit, onLongClick: () -> Unit) { Surface( modifier = Modifier .padding(16.dp) .pointerInput(Unit) { detectTapAndLongPress(onLongClick = onLongClick, onTap = onClick) }, shape = MaterialTheme.shapes.small, color = MaterialTheme.colors.primary ) { Text( text = "Click or long press me", style = MaterialTheme.typography.button, color = Color.White, modifier = Modifier.padding( start = 16.dp, top = 8.dp, end = 16.dp, bottom = 8.dp ) ) } } ``` 这个示例使用了一个自定义的 MyButton 组件,通过 pointerInput() 来同时监听点击和长按事件,并在这两种事件发生时调用不同的回调函数。 另外,这个示例还使用了 Jetpack Compose 中的一些组件,如 Scaffold、Column、Text、BottomAppBar 等,并结合了 ViewModel 来管理计数器的状态,实现了点击或长按按钮可触发计数的效果。 希望这个示例能够对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值