Reputation: 1075
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
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
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
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