前两天在做一个图片选择器的需求,其中的拍照部分要求调用系统相机拍照后允许用户逆时针旋转图片。
旋转Bitmap的方法非常简单,下面的代码将src
文件中的图片读取为Bitmap并旋转了270度,也就是逆时针旋转了90度:
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这个属性值可以理解为拍照的相机顺时针旋装的角度,对一加手机上的照片文件通过如下的方式获取这个字段的值:
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类从文件读取图片的时候则直接丢弃了这些信息,原样读取了照片。
最后附上完整的源代码:
/**
* 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 删除。