5

I have a flow with my custom CameraX like this:

  • Open camera preview (live)
  • Click button to take a photo
  • Have a process when clicking that button (convert the path to bitmap, rotated the image, cropping the image automatically, save into device)
  • After running all the process and successfully, send the image to other Fragment and display it into a glide

The question is when running all the process (in step 3) have a delayed 2 seconds and the camera preview still live (not freeze or lock). How to make camera preview freeze or lock when running the process?

This is my code to running camera preview in 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)
        }

    }
R Rifa Fauzi Komara
  • 1,915
  • 6
  • 27
  • 54

1 Answers1

6

Maybe you can try to unbind the preview use case :

version 1.0.0-alpha06: CameraX.unbind(preview);

version > 1.0.0-alpha07: cameraProvider.unbind(preview);

In your case, you need to save the preview use case into a variable to then unbind it:

// Declare the preview use case variable (as in the CameraXBasic example)
private var preview: Preview? = null

Then instantiate the variable (like you did):

private fun buildPreviewUseCase(): Preview {
    preview = Preview(
        UseCaseConfigBuilder.buildPreviewConfig(
            viewFinder.display
        )
    )
    preview.setOnPreviewOutputUpdateListener { previewOutput ->
        updateViewFinderWithPreview(previewOutput)
        correctPreviewOutputForDisplay(previewOutput.textureSize)
    }
    return preview
}

Then, when you want to freeze the preview just unbind the use case:

CameraX.unbind(preview);

EDIT As @Billda said in this post : CameraX - crash when unbinding Preview UseCase :

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.

So I decided to update my answer to give another solution implementing an analyser with ImageAnalysis (1.0.0-beta02).

1- Create the 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 https://stackoverflow.com/questions/56772967/converting-imageproxy-to-bitmap
        // 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- Initialize the imageAnalyser

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- Bind the imageAnalysis use case

try {
    val camera = cameraProvider.bindToLifecycle(
        this,
        cameraSelector,
        preview,
        imageAnalysis,
        imageCapture
    )
    preview.setSurfaceProvider(preview_view.createSurfaceProvider(camera.cameraInfo))
}

5- Capture the picture

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- Release

btn_release.setOnClickListener {
    preview_view.visibility = View.VISIBLE
    image_view.visibility = View.INVISIBLE
}

I hope it helps, I'm not an expert so if you have some improvements you are welcome !

LaurentP22
  • 586
  • 5
  • 15
  • what code in the `preview` ? can you give me full source code? – R Rifa Fauzi Komara Jan 10 '20 at 02:13
  • Where I add `CameraX.unbind(preview)` function? I try inside `onImageSaved()` it's forclose – R Rifa Fauzi Komara Jan 10 '20 at 04:36
  • Yes inside the `onImageSaved()`, I tested it with the CameraXBasic example and it works. I think it comes from your executor, can you try to do as in the CameraXBasic example using `ContextCompat.getMainExecutor(requireContext())` – LaurentP22 Jan 10 '20 at 15:22
  • Yeah, it's work after I add that into my code. But, why not freeze immediately after I click the camera button? There is still a delay before immediately freeze. – R Rifa Fauzi Komara Jan 13 '20 at 02:25
  • Well I think there is a delay between the moment you click on the button and the moment the callback is called (time for the camera to take the picture).Try to unbind the preview in the `onClickListener`, just after calling `capture.takePicture()`. Nevertheless, the freezed preview can be different to the photo taken. – LaurentP22 Jan 13 '20 at 03:03
  • Ahh I see, it's not good to unbind the preview in the `onClickListener` after calling `capture.takePicture()`. But how if I handle a delay between the moment I click on the button and the moment the callback is called (time for the camera to take the picture). It's possible? – R Rifa Fauzi Komara Jan 13 '20 at 06:55
  • The last thing that comes to me is to add a delay as in the cameraXBasic example: `container.postDelayed({CameraX.unbind(preview)}, 150L)`. Good luck. – LaurentP22 Jan 13 '20 at 15:24
  • @LaurentP22 Just one question that I'm curious about since `imageAnalyzer` is returning bitmap in `FreezeCallback` why add `ImageCapture` use case, the bitmap can be processed in the background thread (Cropped and saved to a file) – Mirwise Khan Jun 09 '20 at 09:35
  • Well depending on the situation I imagine there is no problem to remove the `ImageCapture` use case and then process the bitmap as you said. However there are some advantages using the `ImageCapture` use case: When taking a picture with the `ImageCapture` use case some information are saved with the photo (device model, focal length…). Moreover the quality of the picture might be better. – LaurentP22 Jun 09 '20 at 16:40
  • Thanks for your answer. I would like to use it, but my Android Studio can't recognize `FreezeCallback`. Do I need some non-standard dependency? – Sk1X1 Apr 19 '22 at 14:55
  • maybe use `ImageCapture.OnImageSavedCallback.onImageSaved(ImageCapture.OutputFileResults)` outputFileResults.getSavedUri to ImageView.setImageURI(Uri). one step is save; two step is read; – 卡尔斯路西法 Jun 25 '23 at 02:10