oyeraghib
oyeraghib

Reputation: 1075

How to display Image taken using Camerax API (Android)

How to display the Image taken using CameraX in Android.

My case : I have two fragments. In one of the fragments I have implemented the CameraX functionality to take the photo and save it locally. And now I want to pass this image to an another fragment and display it there in an ImageView.

What should be the best approach for that ( and if you could provide a working code that would be helpful )

Approaches that I think of to solve this:

1st approach : Send the Uri Id that is extracted from overriding onImageSaved (we get the Uri id in ImageCapture.OutputFileResults). But there are few issues with this. Sending the Uri as bundle or safe args using Android Navigation show it as null. Which means there is no transfer of Uri across navigation.

2nd approach : Convert the uri into a bitmap and then send this bitmap to the other fragment. There decode the bitmap to put it on an ImageView. I am not sure of this approach, if you have worked with this before let me know.

Are there any other ways to tackle this issue. I can't find any article on the Official Android Docs for this thing.

Camera Fragment ( 1st Fragment ) :

class CameraFragment : Fragment() {

    private var _binding: FragmentCameraBinding? = null
    private val binding: FragmentCameraBinding get() = _binding!!

    private var imageCapture: ImageCapture? = null
    private lateinit var cameraExecutor: ExecutorService

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        _binding = FragmentCameraBinding.inflate(inflater, container, false)

        //Request Camera Permissions
        // Request camera permissions
        if (allPermissionsGranted()) {
            startCamera()
        } else {
            ActivityCompat.requestPermissions(
                requireActivity(), REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS
            )
        }
        cameraExecutor = Executors.newSingleThreadExecutor()

        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        //Setting up listeners for taking Photo
        binding.imageCaptureButton.setOnClickListener {
            takePhoto()
        }
    }

    private fun takePhoto() {
        // Get a stable reference of the modifiable image capture use case
        val imageCapture = imageCapture ?: return

        // Create time stamped name and MediaStore entry.
        val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
            .format(System.currentTimeMillis())
        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, name)
            put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
                put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image")
            }
        }


        // Create output options object which contains file + metadata
        val outputOptions = ImageCapture.OutputFileOptions
            .Builder(
                requireContext().contentResolver,
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                contentValues
            )
            .build()

        // Set up image capture listener, which is triggered after photo has
        // been taken
        imageCapture.takePicture(
            outputOptions,
            ContextCompat.getMainExecutor(requireContext()),
            object : ImageCapture.OnImageSavedCallback {
                override fun onError(exc: ImageCaptureException) {
                    Timber.e("Photo capture failed: ${exc.message}", exc)
                }

                override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                    val msg = "Photo capture succeeded: ${output.savedUri}"
                    Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT).show()
                    Timber.d(msg)


                    val uriFilePath = output.savedUri?.path
                    val path: String = uriFilePath.toString()

                    val bundle = Bundle()
                    bundle.putString("image", path)

                    Navigation.findNavController(requireView()).navigate(R.id
                        .cameraFragmentToCameraFinalFragment, bundle)
                }
            })
    }
 private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
        ContextCompat.checkSelfPermission(
            requireContext(), it
        ) == PackageManager.PERMISSION_GRANTED
    }


    private fun startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())

        cameraProviderFuture.addListener({
            // Used to bind the lifecycle of cameras to the lifecycle owner
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            // Preview
            val preview = Preview.Builder()
                .build()
                .also { preview ->
                    preview.setSurfaceProvider(binding.viewFinder.surfaceProvider)
                }

            imageCapture = ImageCapture.Builder()
                .build()

            val imageAnalyzer = ImageAnalysis.Builder()
                .setTargetResolution(Size(1280, 720))
                .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                .build()
                .also {
                    it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma ->
                        Timber.d("Average luminosity: $luma")
                    })
                }

            // Select back camera as a default
            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

            try {
                // Unbind use cases before rebinding
                cameraProvider.unbindAll()

                // Bind use cases to camera
                cameraProvider.bindToLifecycle(
                    this, cameraSelector, preview, imageCapture, imageAnalyzer
                )

            } catch (exc: Exception) {
                Timber.e("Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(requireContext()))
    }

    override fun onRequestPermissionsResult(
        requestCode: Int, permissions: Array<String>, grantResults:
        IntArray
    ) {
        if (requestCode == REQUEST_CODE_PERMISSIONS) {
            if (allPermissionsGranted()) {
                startCamera()
            } else {
                Toast.makeText(
                    requireContext(), "Permissions not granted by the user.",
                    Toast.LENGTH_SHORT
                ).show()
            }
        }
    }


    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
        cameraExecutor.shutdown()
    }

    companion object {
        private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
        private const val REQUEST_CODE_PERMISSIONS = 10
        private val REQUIRED_PERMISSIONS = mutableListOf(
            Manifest.permission.CAMERA,
        ).apply {
            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
                add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
            }
        }.toTypedArray()
    }
}

PhotoFinalFragment (2nd Fragment) :

class PhotoFinalFragment : Fragment() {

    private var _binding: FragmentPhotoFinalBinding? = null
    private val binding: FragmentPhotoFinalBinding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        _binding = FragmentPhotoFinalBinding.inflate(inflater, container, false)

        val bundle: Bundle? = arguments?.getBundle("image")
        val uri: Uri = bundle?.get("image") as Uri

        binding.imageView.setImageURI(uri)

        binding.btnYes.setOnClickListener {
            Navigation.findNavController(requireView()).navigate(R.id.cameraFinalFragmentToStoryFragment)
        }

        binding.btnNo.setOnClickListener {
            Navigation.findNavController(requireView()).navigate(R.id.cameraFinalFragmentTocameraFragment)
        }
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

Upvotes: 3

Views: 3126

Answers (3)

ibrahim
ibrahim

Reputation: 91

I took picture from camera in MainActivity using cameraX. Then on onImageSaved method passed image Uri to next activity as

val i = Intent(this@MainActivity, ProcessImageActivity::class.java)
                i.putExtra("imagePath", selectedImageUri.toString())
                startActivity(i)

Then in onCreate method of PrcessImageActivity get the Uri and convert it to Bitmap as

 val imagePath = intent.getStringExtra("imagePath")
    val imageUri = Uri.parse(imagePath)
    takeImageBitmap = getBitmap(contentResolver, imageUri)!!

Then showed the bitmap as

    Column(modifier = Modifier
                .fillMaxWidth()
                .fillMaxHeight()
            ) {
                Image(
                    bitmap = takeImageBitmap.asImageBitmap(),
                    contentDescription = "Process this image",
                )
            }

getBitmap function code is following

fun getBitmap(contentResolver: ContentResolver, fileUri: Uri?): Bitmap? {
    return try {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            ImageDecoder.decodeBitmap(ImageDecoder.createSource(contentResolver, fileUri!!))
        } else {
            MediaStore.Images.Media.getBitmap(contentResolver, fileUri)
        }
    } catch (e: Exception){
        null
    }
}

Upvotes: 0

Mahesh
Mahesh

Reputation: 135

onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
    Uri selectedImageUri = outputFileResults.getSavedUri();
    Bitmap imageBitmap = BitmapUtils.loadFromUri(MainActivity.this, selectedImageUri);

    imgView.setImageBitmap(imageBitmap);
}

oncapture success there is an overide method onimage saved from that you can set the image to.

Upvotes: 0

Sidharth Manoj
Sidharth Manoj

Reputation: 11

Use ImageCapture.OnImageCapturedCallback instead of ImageCapture.OnImageSavedCallback

When using OnImageCapturedCallback(), you get to override onCaptureSuccess which provides the ImageProxy which can be converted into an image and set to your ImageView.

Checkout OnImageCapturedCallback

val buffer = imageProxy.planes[0].buffer
val bytes = ByteArray(buffer.capacity())
buffer[bytes]
val bitmapImage = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
imageProxy.close()

imageView.rotation = imageProxy.imageInfo.rotationDegrees.toFloat()
imageView.setImageBitmap(bitmapImage)

Upvotes: 1

Related Questions