歡迎光臨
每天分享高質量文章

Exif圖片方向的一些發現

作者:少年阿濤

連結: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, 00, 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, 00, 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、資料庫、運維等。

贊(0)

分享創造快樂