查看原文
其他

分享一个困惑了我很久的知识点 | Exif

少年阿涛 鸿洋 2019-04-05

本文作者


作者:少年阿涛

链接:

https://juejin.im/post/5bc3fbcc5188255c672ed754

本文由作者授权发布。


之前我在做相机的时候,知道部分设备拍摄完成后预览图片会需要旋转,并且该旋转信息存储在 EXIF 中,网络上也有一些较为成熟的旋转处理代码,很多时候贴过来用就行了,一直不清楚其原理,直到这篇文章,才让我彻底弄清楚该如何通用的的处理旋转!


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开发的时候,一般需要考虑方向的问题,但是在日常生活,这个也是暴露我们隐私的入口,所以手机在拍照的时候,最好将保存位置这些选项关闭,避免泄漏自己的隐私。




这篇文章让我们弄清楚了,当我们需要预览一张照片时,不太清楚其需要选择多少角度时,有了通用的做法:


读取orientation 值,然后减1,转化为3位的2进制的值,例如101,根据二进制的值进行转化即可,每一位为1,则需要做固定的转化,代码以及转化规则都在上文。



推荐阅读

一篇不错的自定义 View 实践文章

androidx 你好 ,android.support 再见。


扫一扫 关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~


┏(^0^)┛明天见!


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存