# 『消失的圖片屬性,不要旋轉我 』- ExifInterface 用途
最近剛好遇到個Bug,從相簿選擇照片傳到後端,在等後端吐回圖片預覽網址後,圖片就旋轉了,於是就開始了一系列的排除及增長見聞的部分,把此記錄成文章,順便分享給大家。
## 什麼是ExifInterface
### Exif
```
Exif是一種影像檔案格式,它的資料儲存與JPEG格式是完全相同的。
實際上Exif格式就是在JPEG格式頭部插入了數位照片的訊息,
包括拍攝時的光圈、快門、白平衡、ISO、焦距、日期時間等
各種和拍攝條件以及相機品牌、型號、色彩編碼、拍攝時錄製的聲音
以及GPS全球定位系統資料、縮圖等。
```
當然,上面也包含了這次的主題『旋轉』,舉例來說,當你斜斜的拍照時,幾年前你看到的照片就會是斜斜的,但現在幾乎都會自動幫你校正回歸,因為他儲存的時候,Exif資訊(包含了"原圖" + "我應該傾斜幾度的資訊"(當然還有其他的)),因此當在寫Code時,只需要
1. 把他的Exif讀取出來
2. 把傾斜的角度拿取旋轉原圖
3. 在顯示經過旋轉的圖片
這樣就可以呈現一張被自動校正回歸的圖片了。
### ExifInterface
就是在Android中要處理圖片讀取、寫入、縮圖的原生工具類,用他就能去拿到File裡面的Exif資訊,
1. 可以讀取Exif資料
2. 也可以自己寫對應的Exif資料進去
3. 更可以在他有縮圖時直接拿他的縮圖用
## What's wrong?
會發現這個問題,主要是因為有些圖片在手機預覽時是正的,但經過傳到後端後,再給的照片顯示就被旋轉過了,一開始以為是後段自動旋轉了,但結果後端的自動旋轉是利用上面Exif的應用(讀取Exif,在自動幫忙旋轉到正確位置)。
於是就順著這往下查,發現在選擇相簿時,沒有去跑自動旋轉,當初做的時候想說是使用者選的,不應該自動幫他旋轉,但後來發現其實手機的相簿、瀏覽器都有做,反倒是不自動旋轉才會讓使用者困惑。
而後端又說看資料發現我們給他的資訊裡面本身就沒含Exif,所以他們現在是沒有幫我們旋轉的。
### 解決方法
於是目前知道了幾個可能的方向、知識點:
1. Exif遺失
2. 後端實際上並沒有旋轉圖片,也就是原本傳給他的就是我們以為歪的
Finally,找到了問題:
1. Bitmap,並不包含Exif的資訊,Exif的資訊會儲存在File裡
=> 而我們傳給後端是Bitmap去轉的
2. 壓縮圖片時會把Exif資訊給丟失
3. 在相機拍攝照片時,是有用ExifInterface的,也有做旋轉照片的處理
於是:
1. 先把相簿選擇出來的照片,在壓縮上傳前,
1. 讀取原本File的Exif資訊,取得旋轉的資訊並轉換成角度
2. 把bitmap先做好旋轉
3. 拿旋轉好的bitmap 去壓縮 & 傳給後端 => 搞定
2. 此時開始擔心,那會不會後端又幫我旋轉呢?
=> 但不會有此問題,因為Bitmap 沒有Exif資訊,因此後端也不會幫你旋轉(如果你家的後端會幫你轉的話... 請自行先喬好XD)
## 實用方法&觀念提醒
1. ExifInterface要引用後才能用
`implementation("androidx.exifinterface:exifinterface:1.3.7")`
2. Exif資訊 File才有,送一個讀取方法
- [各資訊意思請參考此篇文章](https://blog.csdn.net/u011002668/article/details/51490712)
- 經緯度要取的話,Android10以後要多拿權限 [參考文章](https://jefflin1982.medium.com/android-exifinterface%E5%9C%A8android10%E4%B9%8B%E5%BE%8C%E7%84%A1%E6%B3%95%E5%8F%96%E5%BE%97%E7%85%A7%E7%89%87%E7%9A%84latlongps%E8%B3%87%E8%A8%8A-77f43e5d4283)
```
/**
* 印出File的Exif資訊
*/
fun readExif(file: File) {
try {
val exif = ExifInterface(file.absolutePath)
val attributes = listOf(
ExifInterface.TAG_DATETIME,
ExifInterface.TAG_FLASH,
ExifInterface.TAG_FOCAL_LENGTH,
ExifInterface.TAG_GPS_ALTITUDE,
ExifInterface.TAG_GPS_ALTITUDE_REF,
ExifInterface.TAG_GPS_DATESTAMP,
ExifInterface.TAG_GPS_LATITUDE,
ExifInterface.TAG_GPS_LATITUDE_REF,
ExifInterface.TAG_GPS_LONGITUDE,
ExifInterface.TAG_GPS_LONGITUDE_REF,
ExifInterface.TAG_GPS_PROCESSING_METHOD,
ExifInterface.TAG_GPS_TIMESTAMP,
ExifInterface.TAG_IMAGE_LENGTH,
ExifInterface.TAG_IMAGE_WIDTH,
ExifInterface.TAG_MAKE,
ExifInterface.TAG_MODEL,
ExifInterface.TAG_ORIENTATION,
ExifInterface.TAG_WHITE_BALANCE
)
for (attribute in attributes) {
val value = exif.getAttribute(attribute)
if (value != null) {
println("$attribute: $value")
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
```
3. 取得旋轉後的Bitmap
```
/**
* 取得旋轉後的Bitmap
*/
fun getRotatedBitmap(file: File): Bitmap? {
val bitmap = BitmapFactory.decodeFile(file.absolutePath) ?: return null
var degree = 0
try {
val exifInterface = ExifInterface(file.absolutePath)
degree = when (exifInterface.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL
)) {
ExifInterface.ORIENTATION_ROTATE_90 -> 90
ExifInterface.ORIENTATION_ROTATE_180 -> 180
ExifInterface.ORIENTATION_ROTATE_270 -> 270
else -> 0
}
} catch (e: Exception) {
e.printStackTrace()
}
//依Exif提供的角度旋轉圖片
val matrix = Matrix()
matrix.postRotate(degree.toFloat())
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
}
```
4. 其他更多操作可以看 [Android操作Exif-ExifInterface全解析](https://blog.csdn.net/u011002668/article/details/51490712)
## 參考資料
[- 官方文件](https://developer.android.com/jetpack/androidx/releases/exifinterface)
[- Android操作Exif-ExifInterface全解析](https://blog.csdn.net/u011002668/article/details/51490712)
[- ExifInterface在Android10之後無法取得照片的LatLonGPS資訊](https://jefflin1982.medium.com/android-exifinterface%E5%9C%A8android10%E4%B9%8B%E5%BE%8C%E7%84%A1%E6%B3%95%E5%8F%96%E5%BE%97%E7%85%A7%E7%89%87%E7%9A%84latlongps%E8%B3%87%E8%A8%8A-77f43e5d4283)
[- ExifInterface 常用方法&參數](https://www.jianshu.com/p/6179f16907dc)
[- ExifInterface 支持库简介](https://juejin.cn/post/6844903459930505230)