# 『消失的圖片屬性,不要旋轉我 』- 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)