All images taken with Android CameraX have incorrect orientation in their EXIF data

1.4k Views Asked by At

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
        }
    }
1

There are 1 best solutions below

0
On

use below of code to get exif and rotate of image file. Bitmap don't have exif.

public static Bitmap rotateImage(String path) throws IOException {
    Bitmap bitmap = BitmapFactory.decodeFile(path);
    int rotate = 0;

    ExifInterface exif;
    exif = new ExifInterface(path);
    int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
            ExifInterface.ORIENTATION_NORMAL);
    switch (orientation) {
        case ExifInterface.ORIENTATION_ROTATE_270:
            rotate = 270;
            break;
        case ExifInterface.ORIENTATION_ROTATE_180:
            rotate = 180;
            break;
        case ExifInterface.ORIENTATION_ROTATE_90:
            rotate = 90;
            break;
    }
    Matrix matrix = new Matrix();
    matrix.postRotate(rotate);
    return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
            bitmap.getHeight(), matrix, true);
}