作者:少年阿濤
連結:https://juejin.im/post/5bc3fbcc5188255c672ed754
1、問題
不知道大家有沒有遇到過這樣一個問題,安卓手機拍照預覽圖片是正常的,但是讀取拍照傳回的圖片,卻發現圖片方向是錯的。恰好我就遇到這樣一個問題,圖片如下:
發現沒,圖片逆時針旋轉了九十度。這其實是一個叫Exif的東東在搞鬼。下麵我們就來看看看看這個Exif究竟是何方神聖。
2、背景
首先,先要瞭解Exif是個什麼東東,搬出百度百科
可交換影象檔案格式(英語:Exchangeable image file format,官方簡稱Exif),是專門為數碼相機的照片設定的,可以記錄數碼照片的屬性資訊和拍攝資料。
說到底Exif就是一種格式,用來儲存圖片的一些資訊,這些資訊和我們日常比較相關的有拍攝裝置,拍攝地點,圖片尺寸等,不過今天的主角是另外一個——那就是圖片方向(orientation)。這個圖片方向不是指我們平時使用圖片編輯器旋轉的方向,而是拍照時手機的方向。
總共有八個方向:
下圖是JPEG ORIENTATION對應圖片方向的糾正演演算法,這裡它透過三位二進位制數代表八種方向,然後再透過每一位二進位制數對應不同的操作來對圖片進行糾正,如下:
https://magnushoff.com/jpeg-orientation.html
最高位二進位制數代表對角線翻轉的操作,第二位二進位制數代表旋轉180度的操作,最低位代表水平翻轉的操作。例如001,就是水平翻轉,所以可以看到001的圖形和原圖形關於水平軸對稱。透過把八個方向的圖形用3個二進位制數即三種操作組合,就可以很方便的對圖形做轉換,編碼偽程式碼如下:
if (value & 100b != 0) image.flip-diagonally
if (value & 010b != 0) image.rotate-180
if (value & 001b != 0) image.flip-horizontally
那有人就會困惑了,自己怎麼平時沒有看到這種圖片呢,這是因為我們使用的圖片檢視器或者是瀏覽器對orientation做了相容,會對展示的圖片做轉換。
如下是windows檔案夾的展示:
下麵則是Android Studio的圖片展示
所以可以看到,windows是預設對圖片orientation做了處理,而Android的ImageView則沒有處理所以看到的是圖片本來的方向。
這是八個F的圖片連結
https://magnushoff.com/assets/test-exiforientation.zip
3、應用
在Android裡面,三星手機的拍照是個奇葩的存在,我上面用的手機就是三星手機,三星手機的exif是旋轉90度,別家手機則是0度,所以三星手機的照片需要做處理,這裡是一張三星手機照片的exif資訊:
三星手機的方向是Rotate 90CW,意思就是需要順時針方向(ClockWise)旋轉90度。腦殼轉的快的同學可以對照上面的F圖,相信很快看出是101這張圖。
那我們取出圖片的orientation值進行驗證:
try {
val exifInterface = ExifInterface(resources.openRawResource(id))
val orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
Log.e("orientation", orientation.toString())
} catch (e: IOException) {
e.printStackTrace()
}
列印結果是6,和上面的101對不上,其實在Android的orientation是需要做減1處理的,也就是說6其實對應的是101這種狀態。
另外,需要註意的是,如果列印結果是0,那麼說明圖片沒有orientation這個資訊。
那接下來我們進行編碼,這是第一張方式:
val options = BitmapFactory.Options()
var bitmap = BitmapFactory.decodeResource(resources, R.mipmap.error_orientation, options)
val matrix = Matrix()
matrix.postRotate(getOrientation(R.mipmap.error_orientation).toFloat())
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
imageview.setImageBitmap(bitmap)
private fun getOrientation(id:Int): Int {
var degree = 0
try {
val exifInterface = ExifInterface(resources.openRawResource(id))
val orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
when (orientation) {
ExifInterface.ORIENTATION_ROTATE_90 -> degree = 90
ExifInterface.ORIENTATION_ROTATE_180 -> degree = 180
ExifInterface.ORIENTATION_ROTATE_270 -> degree = 270
}
} catch (e: IOException) {
e.printStackTrace()
}
return degree
}
一般來說,我們只需要處理這三種角度,上面三個角度對應的orientation是6 3 8,也就是101,010,111這三種狀態。為什麼一般只需要處理這三種狀態呢,自己腦補一下拿相機的角度,不外乎就四種情況,除了正常的情況下,不就只需要處理三種情況嗎?
嘿嘿,我真是個小機靈鬼。
當然,如果要嚴謹一點,還是需要按照JPEG那種操作方式來,如下:
val options = BitmapFactory.Options()
var bitmap = BitmapFactory.decodeResource(resources, R.mipmap.f7t, options)
val matrix = genOrientationMatrix(R.mipmap.f7t)
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
imageview.setImageBitmap(bitmap)
private fun genOrientationMatrix(id:Int): Matrix {
val matrix = Matrix()
try {
val exifInterface = ExifInterface(resources.openRawResource(id))
var orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
if (orientation > 0) {
orientation--
if (orientation and 0b100 != 0) { //對角線翻轉
matrix.postScale(-1.0f, 1.0f)
matrix.postRotate(-90f)
}
if (orientation and 0b010 != 0) { //旋轉180度
matrix.postRotate(180f)
}
if (orientation and 0b001 != 0) { //水平翻轉
matrix.postScale(-1.0f, 1.0f)
}
}
return matrix
} catch (e: IOException) {
e.printStackTrace()
}
return matrix
}
其實就是將JPEG對於orientation的轉換利用程式碼進行實現,對矩陣進行相應的變換。
4、總結
Exif是一種儲存了相片一些資訊的格式,平常我們在進行Android開發的時候,一般需要考慮方向的問題,但是在日常生活,這個也是暴露我們隱私的入口,所以手機在拍照的時候,最好將儲存位置這些選項關閉,避免洩漏自己的隱私。
參考
Android效能最佳化:圖片儲存,還能更快https://xiazdong.github.io/2017/03/28/Android%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%EF%BC%9A%E5%9B%BE%E7%89%87%E4%BF%9D%E5%AD%98%EF%BC%8C%E8%BF%98%E8%83%BD%E6%9B%B4%E5%BF%AB/
JPEG Orientation https://magnushoff.com/jpeg-orientation.html
●編號378,輸入編號直達本文
●輸入m獲取到文章目錄
Java程式設計
更多推薦《18個技術類公眾微信》
涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。