拍照时如何使CameraX Preview冻结?
How to make CameraX Preview freeze when take a photo?
我的自定义 CameraX 流程如下:
- 打开相机预览(实时)
- 点击按钮拍照
- 点击该按钮时有一个过程(将路径转换为位图,旋转图像,自动裁剪图像,保存到设备中)
- 完成运行所有流程并成功后,将图片发送到其他Fragment并显示为glide
问题是 running all the process (in step 3)
有 delayed 2 seconds
并且相机预览仍然 live
(not freeze
或 lock
)。如何制作camera preview freeze or lock when running the process
?
这是我在 Camera X 中 运行 相机预览的代码:
class CameraFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_camera, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewFinder.post { setupCamera() }
}
private fun setupCamera() {
CameraX.unbindAll()
CameraX.bindToLifecycle(
this,
buildPreviewUseCase(),
buildImageCaptureUseCase()
)
}
private fun buildPreviewUseCase(): Preview {
val preview = Preview(
UseCaseConfigBuilder.buildPreviewConfig(
viewFinder.display
)
)
preview.setOnPreviewOutputUpdateListener { previewOutput ->
updateViewFinderWithPreview(previewOutput)
correctPreviewOutputForDisplay(previewOutput.textureSize)
}
return preview
}
private fun buildImageCaptureUseCase(): ImageCapture {
val capture = ImageCapture(
UseCaseConfigBuilder.buildImageCaptureConfig(
viewFinder.display
)
)
cameraCaptureImageButton.setOnClickListener {
capture.takePicture(
FileCreator.createTempFile(JPEG_FORMAT),
Executors.newSingleThreadExecutor(),
object : ImageCapture.OnImageSavedListener {
override fun onImageSaved(file: File) {
// I want make a freeze camera preview when execute this before launch *launchGalleryFragment(path)*
val bitmap = BitmapFactory.decodeFile(file.absolutePath)
val rotatedBitmap = bitmap.rotate(90)
val croppedImage = cropImage(rotatedBitmap, viewFinder, rectangle)
val path = saveImage(croppedImage)
requireActivity().runOnUiThread {
launchGalleryFragment(path)
}
}
override fun onError(
imageCaptureError: ImageCapture.ImageCaptureError,
message: String,
cause: Throwable?
) {
Toast.makeText(requireContext(), "Error: $message", Toast.LENGTH_LONG)
.show()
Log.e("CameraFragment", "Capture error $imageCaptureError: $message", cause)
}
})
}
return capture
}
private fun launchGalleryFragment(path: String) {
val action = CameraFragmentDirections.actionLaunchGalleryFragment(path)
findNavController().navigate(action)
}
}
也许你可以尝试解绑预览用例:
版本 1.0.0-alpha06:CameraX.unbind(preview);
版本 > 1.0.0-alpha07:cameraProvider.unbind(preview);
在您的情况下,您需要将预览用例保存到一个变量中,然后再解除绑定:
// Declare the preview use case variable (as in the CameraXBasic example)
private var preview: Preview? = null
然后实例化变量(就像你做的那样):
private fun buildPreviewUseCase(): Preview {
preview = Preview(
UseCaseConfigBuilder.buildPreviewConfig(
viewFinder.display
)
)
preview.setOnPreviewOutputUpdateListener { previewOutput ->
updateViewFinderWithPreview(previewOutput)
correctPreviewOutputForDisplay(previewOutput.textureSize)
}
return preview
}
然后,当您想冻结预览时,只需取消绑定用例即可:
CameraX.unbind(preview);
编辑
正如@Billda 所说 post : :
To freeze a preview you should not unbind Preview usecase. There may
API for that in the future, but currently the recommended way is to
store latest frame from ImageAnalysis and put it to ImageView
overlapping the preview.
所以我决定更新我的答案以提供另一个使用 ImageAnalysis (1.0.0-beta02) 实现分析器的解决方案。
1- 创建 FreezeAnalyzer class:
class FreezeAnalyzer(private val callback: FreezeCallback) : ImageAnalysis.Analyzer {
private var flag = false
override fun analyze(image: ImageProxy) {
if(flag){
flag = false
val bitmap = toBitmap(image)
callback.onLastFrameCaptured(bitmap)
}
image.close()
}
fun freeze(){
flag = true
}
private fun toBitmap(image: ImageProxy): Bitmap {
// Convert the imageProxy to Bitmap
// ref
// ISSUE, on my android 7 when converting the imageProxy to Bitmap I have a problem with the colors...
var bitmap = ...
// Rotate the bitmap
val rotationDegrees = image.imageInfo.rotationDegrees.toFloat()
if (rotationDegrees != 0f) {
val matrix = Matrix()
matrix.postRotate(rotationDegrees)
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
}
return bitmap
}
}
2- XML
<androidx.camera.view.PreviewView
android:id="@+id/preview_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/image_view"
android:visibility="invisible"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
3- 初始化图像分析器
val resolutionSize = Size(preview_view.width, preview_view.height)
// Set up analyser
imageAnalysis = ImageAnalysis.Builder().apply {
setTargetResolution(resolutionSize)
setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
}.build()
val analyzer = FreezeAnalyzer(object : FreezeCallback {
override fun onLastFrameCaptured(bitmap: Bitmap) {
runOnUiThread {
preview_view.visibility = View.INVISIBLE
image_view.visibility = View.VISIBLE
image_view.setImageBitmap(bitmap)
}
}
})
imageAnalysis.setAnalyzer(executor, analyzer)
4- 绑定 imageAnalysis 用例
try {
val camera = cameraProvider.bindToLifecycle(
this,
cameraSelector,
preview,
imageAnalysis,
imageCapture
)
preview.setSurfaceProvider(preview_view.createSurfaceProvider(camera.cameraInfo))
}
5- 抓图
btn_capture.setOnClickListener {
file = File(externalMediaDirs.first(), "${System.currentTimeMillis()}.jpg")
val outputFileOptions: ImageCapture.OutputFileOptions =
ImageCapture.OutputFileOptions.Builder(file!!).build()
analyzer.freeze()
imageCapture.takePicture(outputFileOptions, executor, onImageSavedCallback)
}
6- 发布
btn_release.setOnClickListener {
preview_view.visibility = View.VISIBLE
image_view.visibility = View.INVISIBLE
}
希望对您有所帮助,我不是专家,所以如果您有任何改进,欢迎您!
我的自定义 CameraX 流程如下:
- 打开相机预览(实时)
- 点击按钮拍照
- 点击该按钮时有一个过程(将路径转换为位图,旋转图像,自动裁剪图像,保存到设备中)
- 完成运行所有流程并成功后,将图片发送到其他Fragment并显示为glide
问题是 running all the process (in step 3)
有 delayed 2 seconds
并且相机预览仍然 live
(not freeze
或 lock
)。如何制作camera preview freeze or lock when running the process
?
这是我在 Camera X 中 运行 相机预览的代码:
class CameraFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_camera, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewFinder.post { setupCamera() }
}
private fun setupCamera() {
CameraX.unbindAll()
CameraX.bindToLifecycle(
this,
buildPreviewUseCase(),
buildImageCaptureUseCase()
)
}
private fun buildPreviewUseCase(): Preview {
val preview = Preview(
UseCaseConfigBuilder.buildPreviewConfig(
viewFinder.display
)
)
preview.setOnPreviewOutputUpdateListener { previewOutput ->
updateViewFinderWithPreview(previewOutput)
correctPreviewOutputForDisplay(previewOutput.textureSize)
}
return preview
}
private fun buildImageCaptureUseCase(): ImageCapture {
val capture = ImageCapture(
UseCaseConfigBuilder.buildImageCaptureConfig(
viewFinder.display
)
)
cameraCaptureImageButton.setOnClickListener {
capture.takePicture(
FileCreator.createTempFile(JPEG_FORMAT),
Executors.newSingleThreadExecutor(),
object : ImageCapture.OnImageSavedListener {
override fun onImageSaved(file: File) {
// I want make a freeze camera preview when execute this before launch *launchGalleryFragment(path)*
val bitmap = BitmapFactory.decodeFile(file.absolutePath)
val rotatedBitmap = bitmap.rotate(90)
val croppedImage = cropImage(rotatedBitmap, viewFinder, rectangle)
val path = saveImage(croppedImage)
requireActivity().runOnUiThread {
launchGalleryFragment(path)
}
}
override fun onError(
imageCaptureError: ImageCapture.ImageCaptureError,
message: String,
cause: Throwable?
) {
Toast.makeText(requireContext(), "Error: $message", Toast.LENGTH_LONG)
.show()
Log.e("CameraFragment", "Capture error $imageCaptureError: $message", cause)
}
})
}
return capture
}
private fun launchGalleryFragment(path: String) {
val action = CameraFragmentDirections.actionLaunchGalleryFragment(path)
findNavController().navigate(action)
}
}
也许你可以尝试解绑预览用例:
版本 1.0.0-alpha06:CameraX.unbind(preview);
版本 > 1.0.0-alpha07:cameraProvider.unbind(preview);
在您的情况下,您需要将预览用例保存到一个变量中,然后再解除绑定:
// Declare the preview use case variable (as in the CameraXBasic example)
private var preview: Preview? = null
然后实例化变量(就像你做的那样):
private fun buildPreviewUseCase(): Preview {
preview = Preview(
UseCaseConfigBuilder.buildPreviewConfig(
viewFinder.display
)
)
preview.setOnPreviewOutputUpdateListener { previewOutput ->
updateViewFinderWithPreview(previewOutput)
correctPreviewOutputForDisplay(previewOutput.textureSize)
}
return preview
}
然后,当您想冻结预览时,只需取消绑定用例即可:
CameraX.unbind(preview);
编辑
正如@Billda 所说 post :
To freeze a preview you should not unbind Preview usecase. There may API for that in the future, but currently the recommended way is to store latest frame from ImageAnalysis and put it to ImageView overlapping the preview.
所以我决定更新我的答案以提供另一个使用 ImageAnalysis (1.0.0-beta02) 实现分析器的解决方案。
1- 创建 FreezeAnalyzer class:
class FreezeAnalyzer(private val callback: FreezeCallback) : ImageAnalysis.Analyzer {
private var flag = false
override fun analyze(image: ImageProxy) {
if(flag){
flag = false
val bitmap = toBitmap(image)
callback.onLastFrameCaptured(bitmap)
}
image.close()
}
fun freeze(){
flag = true
}
private fun toBitmap(image: ImageProxy): Bitmap {
// Convert the imageProxy to Bitmap
// ref
// ISSUE, on my android 7 when converting the imageProxy to Bitmap I have a problem with the colors...
var bitmap = ...
// Rotate the bitmap
val rotationDegrees = image.imageInfo.rotationDegrees.toFloat()
if (rotationDegrees != 0f) {
val matrix = Matrix()
matrix.postRotate(rotationDegrees)
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
}
return bitmap
}
}
2- XML
<androidx.camera.view.PreviewView
android:id="@+id/preview_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/image_view"
android:visibility="invisible"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
3- 初始化图像分析器
val resolutionSize = Size(preview_view.width, preview_view.height)
// Set up analyser
imageAnalysis = ImageAnalysis.Builder().apply {
setTargetResolution(resolutionSize)
setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
}.build()
val analyzer = FreezeAnalyzer(object : FreezeCallback {
override fun onLastFrameCaptured(bitmap: Bitmap) {
runOnUiThread {
preview_view.visibility = View.INVISIBLE
image_view.visibility = View.VISIBLE
image_view.setImageBitmap(bitmap)
}
}
})
imageAnalysis.setAnalyzer(executor, analyzer)
4- 绑定 imageAnalysis 用例
try {
val camera = cameraProvider.bindToLifecycle(
this,
cameraSelector,
preview,
imageAnalysis,
imageCapture
)
preview.setSurfaceProvider(preview_view.createSurfaceProvider(camera.cameraInfo))
}
5- 抓图
btn_capture.setOnClickListener {
file = File(externalMediaDirs.first(), "${System.currentTimeMillis()}.jpg")
val outputFileOptions: ImageCapture.OutputFileOptions =
ImageCapture.OutputFileOptions.Builder(file!!).build()
analyzer.freeze()
imageCapture.takePicture(outputFileOptions, executor, onImageSavedCallback)
}
6- 发布
btn_release.setOnClickListener {
preview_view.visibility = View.VISIBLE
image_view.visibility = View.INVISIBLE
}
希望对您有所帮助,我不是专家,所以如果您有任何改进,欢迎您!