I am using CameraX https://developer.android.com/training/camerax to take some images. However, all my images come out the wrong rotation. They are all marked: ORIENTATION_ROTATE_90
Yes, I have checked to ensure that the orientation lock is not on and there is nothing in the manifest to lock the screen orientation nor am I overriding any orientation method.
No matter how I test it via simulator or real device, the orientation seems to be "locked". The only picture that turns out correctly rotated, is when the device is in portrait mode. However, it's still flagged as ORIENTATION_ROTATE_90
Can anyone see what I might be doing incorrectly?
private var imageCapture: ImageCapture? = null
private lateinit var cameraExecutor: ExecutorService
//.. other methods removed for brievity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Request camera permissions
cameraExecutor = Executors.newSingleThreadExecutor()
}
override fun onResume() {
super.onResume()
startCamera()
}
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 {
it.setSurfaceProvider(binding.cameraPreview.surfaceProvider)
}
imageCapture = Builder().build()
// Doesn't work
// activity?.display.let { d ->
// d.let { imageCapture!!.targetRotation = d!!.rotation }
// }
// 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)
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(requireContext()))
}
private fun captureImage() {
// Get a stable reference of the modifiable image capture use case
val imageCapture = imageCapture ?: return
// this way the images will stay in order
val id = System.currentTimeMillis().toString()
// Make directory if it doesn't exist, and build a file for the new image to go into
val photoFile = File("${sharedViewModel.fileDirectory}/${id}").apply {
@Suppress("RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
parentFile.mkdirs()
}
// Create output options object which contains file + metadata
val outputOptions = OutputFileOptions.Builder(photoFile).build()
// Set up image capture listener, which is triggered after photo has been taken
imageCapture.takePicture(
outputOptions, ContextCompat.getMainExecutor(requireContext()), object : OnImageSavedCallback {
override fun onError(exc: ImageCaptureException) {
Toast.makeText(requireContext(), "Photo capture failed: ${exc.message}", Toast.LENGTH_LONG).show()
Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
}
override fun onImageSaved(output: OutputFileResults) {
fixRotation(photoFile.path)
}
})
}
private fun fixRotation(imageUri: String) {
val bitmap = File(imageUri)
if (!bitmap.exists()) return
Uri.parse(imageUri)?.let{
CoroutineScope(Dispatchers.IO).launch { spinAndSave(it) }
}
}
private suspend fun spinAndSave(imageURI: Uri) = withContext(Dispatchers.IO) {
val options = BitmapFactory.Options()
options.inPreferredConfig = Bitmap.Config.ARGB_8888
imageURI.path?.let { path ->
ExifInterface(path).getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED).let { orientation ->
debugOrientation(orientation)
val rotatedBitmap = rotateBitmap( BitmapFactory.decodeFile(path, options), orientation)!!
FileOutputStream(imageURI.path).use { fos ->
rotatedBitmap.compress(Bitmap.CompressFormat.JPEG, 50, fos)
Log.i(TAG,"New Image Saved")
}
}
}
}
private fun debugOrientation(orientation: Int) {
val o = when (orientation) {
ExifInterface.ORIENTATION_NORMAL -> "ORIENTATION_NORMAL"
ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> "ORIENTATION_FLIP_HORIZONTAL"
ExifInterface.ORIENTATION_ROTATE_180 -> "ORIENTATION_ROTATE_180"
ExifInterface.ORIENTATION_FLIP_VERTICAL -> "ORIENTATION_FLIP_VERTICAL"
ExifInterface.ORIENTATION_TRANSPOSE -> "ORIENTATION_TRANSPOSE"
ExifInterface.ORIENTATION_ROTATE_90 -> "ORIENTATION_ROTATE_90"
ExifInterface.ORIENTATION_TRANSVERSE -> "ORIENTATION_TRANSVERSE"
ExifInterface.ORIENTATION_ROTATE_270 -> "ORIENTATION_ROTATE_270"
else -> "UKNONWN ORIENTATION"
}
Log.w(TAG,"ORIEntation int: $orientation is: $o")
}
private fun rotateBitmap(bitmap: Bitmap, orientation: Int): Bitmap? {
val matrix = Matrix()
when (orientation) {
ExifInterface.ORIENTATION_NORMAL -> return bitmap
ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.setScale(-1f, 1f)
ExifInterface.ORIENTATION_ROTATE_180 -> matrix.setRotate(180f)
ExifInterface.ORIENTATION_FLIP_VERTICAL -> {
matrix.setRotate(180f)
matrix.postScale(-1f, 1f)
}
ExifInterface.ORIENTATION_TRANSPOSE -> {
matrix.setRotate(90f)
matrix.postScale(-1f, 1f)
}
ExifInterface.ORIENTATION_ROTATE_90 -> matrix.setRotate(90f)
ExifInterface.ORIENTATION_TRANSVERSE -> {
matrix.setRotate(-90f)
matrix.postScale(-1f, 1f)
}
ExifInterface.ORIENTATION_ROTATE_270 -> matrix.setRotate(-90f)
else -> return bitmap
}
return try {
val bmRotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
bitmap.recycle()
bmRotated
} catch (e: OutOfMemoryError) {
e.printStackTrace()
null
}
}
use below of code to get exif and rotate of image file. Bitmap don't have exif.