前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android旋转相机拍摄的照片

Android旋转相机拍摄的照片

原创
作者头像
雪之下Perqin
发布2021-08-26 10:23:59
1.2K0
发布2021-08-26 10:23:59
举报
文章被收录于专栏:雪之下同学的开发专栏

前两天在做一个图片选择器的需求,其中的拍照部分要求调用系统相机拍照后允许用户逆时针旋转图片。

旋转Bitmap的方法非常简单,下面的代码将src文件中的图片读取为Bitmap并旋转了270度,也就是逆时针旋转了90度:

代码语言:txt
复制
val srcBitmap = BitmapFactory.decodeFile(src.absolutePath)
val rotatedBitmap = Bitmap.createBitmap(srcBitmap, 0, 0, srcBitmap.width, srcBitmap.height, Matrix().apply {
    postRotate(270F)
}, true)

然而,在实际运行的时候却发现应该旋转270度的却只旋转了180度,每个方向的旋转都少了90度。

在另一部华为手机上运行,惊奇地发现在这部手机上一切正常,难道是一加的工程师又魔改了系统?借了同事的谷歌手机,发现和一加的表现一致,看来不是系统的问题。

一筹莫展之际,我随手打开了单步调试,在Android Studio中预览了srcBitmap,此时才发现从文件中读取的图片竟然已经被旋转了270度,而通过Glide将图片文件加载给ImageView的时候却是朝向正常的。看着Logcat中不知为何出现的EXIF相关的日志信息,我突然猜想:是否照片中的EXIF中包含了照片朝向呢?

谷歌之后发现,EXIF信息中确实包含了图片朝向,查阅Wiki可以看到确实有Orientation (rotation)这个属性。根据Wiki提供的参考链接可以得知三种非常规朝向和对应的值,如下图:

Orientation这个属性值可以理解为拍照的相机顺时针旋装的角度,对一加手机上的照片文件通过如下的方式获取这个字段的值:

代码语言:txt
复制
val exif = ExifInterface(src)
val requiredRotation = when (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)) {
    ExifInterface.ORIENTATION_ROTATE_90 -> 90
    ExifInterface.ORIENTATION_ROTATE_180 -> 180
    ExifInterface.ORIENTATION_ROTATE_270 -> 270
    else -> 0
}

会发现这部手机的照片的Orientation值是6,也就是需要顺时针旋转90度才能“摆正”。

找到原因后,解决办法也非常简单了:在旋转之前先旋转一定的角度摆正照片,再追加需要旋转的角度。例如在我的一加手机上,当需要旋转180度时,实际需要旋转的角度就是90+180=270度。

使用上述方法之后,得到的图片果然符合预期了,看来Glide以及系统相册应用等都能正确处理照片EXIF中的朝向信息,而Bitmap类从文件读取图片的时候则直接丢弃了这些信息,原样读取了照片。

最后附上完整的源代码:

代码语言:txt
复制
/**
 * Rotate [src] image file by the specified [degree], and write the resulting bitmap into [dest]. This function tries to
 * avoid unnecessary rotation by inspecting the EXIF information first.
 * [src] and [dest] are allowed to be the same file, as this function reads [src] into memory first.
 */
suspend fun rotateImageFile(src: File, dest: File, degree: Int) = withContext(Dispatchers.IO) {
    v(TAG, "rotateImageFile: src = $src, dest = $dest, degree = $degree")
    if (degree == 0) {
        // No rotation required for the file
        v(TAG, "rotateImageFile: No rotation required for the file")
        if (src == dest) {
            v(TAG, "rotateImageFile: No writing required for the file")
            return@withContext
        }
        src.copyTo(dest, true)
        return@withContext
    }
    val exif = ExifInterface(src)
    // Required rotation to make the encoded image be in normal orientation
    val requiredRotation = when (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)) {
        ExifInterface.ORIENTATION_ROTATE_90 -> 90
        ExifInterface.ORIENTATION_ROTATE_180 -> 180
        ExifInterface.ORIENTATION_ROTATE_270 -> 270
        else -> 0
    }
    // Degree that required for the decoded bitmap. Note that the BitmapFactory ignores the EXIF information, so the decoded
    // bitmap may be already rotated.
    val degreeForBitmap = (requiredRotation + degree) % 360
    val srcBitmap = BitmapFactory.decodeFile(src.absolutePath)
    val rotatedBitmap = Bitmap.createBitmap(srcBitmap, 0, 0, srcBitmap.width, srcBitmap.height, Matrix().apply {
        postRotate(degreeForBitmap.toFloat())
    }, true)
    val successful = FileOutputStream(dest).use {
        rotatedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, it)
    }
    if (!successful) {
        throw IOException("Fail to write bitmap into file $dest")
    }
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档