問題

Android Q以降はImageDecoder.decodeBitmap()、それ以前はMediaStore.Images.Media.getBitmap()でBitmapを取得し、EXIFから取得した回転情報を適用する実装がある。

val (bitmap, rotation) =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        val bmp = ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, uri))
        bmp to getRotation(uri, context)
    } else {
        @Suppress("DEPRECATION")
        val bmp = MediaStore.Images.Media.getBitmap(context.contentResolver, uri)
        bmp to getRotation(uri, context)
    }

val matrix = Matrix()
matrix.setRotate(rotation.toFloat())
val rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)

getRotationはEXIFのOrientation情報から回転角度を返す。

private fun getRotation(
    uri: Uri?,
    context: Context,
): Int {
    uri ?: return 0

    val orientation =
        context.contentResolver.openInputStream(uri)?.use { inputStream ->
            ExifInterface(inputStream).getAttributeInt(
                ExifInterface.TAG_ORIENTATION,
                ExifInterface.ORIENTATION_NORMAL,
            )
        } ?: ExifInterface.ORIENTATION_NORMAL

    return when (orientation) {
        ExifInterface.ORIENTATION_ROTATE_90 -> 90
        ExifInterface.ORIENTATION_ROTATE_180 -> 180
        ExifInterface.ORIENTATION_ROTATE_270 -> 270
        else -> 0
    }
}

この実装では、ImageDecoder.decodeBitmap()経由で作成したBitmapに対して2重に回転が適用される。

原因

ImageDecoder.decodeBitmap()はEXIFのOrientation情報を自動で読み取り、Bitmapを回転して返す。

そのため、getRotation()で取得した角度をさらに適用すると、回転が2重になる。

一方、MediaStore.Images.Media.getBitmap()はEXIFを無視してBitmapを返すため、getRotation()による手動回転が必要になる。

修正

Android Q以降はImageDecoder.decodeBitmap()が自動で回転するため、手動回転の角度を0にする。

val (bitmap, rotation) =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        val bmp = ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, uri))
        bmp to 0
    } else {
        @Suppress("DEPRECATION")
        val bmp = MediaStore.Images.Media.getBitmap(context.contentResolver, uri)
        bmp to getRotation(uri, context)
    }

val matrix = Matrix()
matrix.setRotate(rotation.toFloat())
val rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)