【问题标题】:Is it possible to make BitmapFactory.decodeFile() respect EXIF?是否可以让 BitmapFactory.decodeFile() 尊重 EXIF?
【发布时间】:2018-01-15 01:35:59
【问题描述】:

在我的测试中,BitmapFactory.decodeFile() 创建的 Bitmap 不遵守 EXIF 标头。

例如,当我调用Bitmap.getWidth()Bitmap.getHeight() 时,设备拍摄的纵向图像不会根据相机方向旋转实际像素数据,而是将其存储在 EXIF 标头中,它们返回不正确的值(高度为宽度,反之亦然)。

有没有办法让BitmapFactory.decodeFile() 尊重EXIF 并产生正确的Bitmap

如果不是,处理此问题的推荐模式是什么?

没有经验丰富的 Android 开发人员的建议,我看到的唯一方法是预处理拍摄的图像(加载、根据 EXIF 旋转和保存)。但除了巨大的处理开销之外,这可能会导致OutOfMemoryExceptions 用于较大的相机分辨率(在您无法通过使用BitmapFactory.Options.inSampleSize 加载缩小的图像来降低质量的情况下)。

【问题讨论】:

    标签: java android image bitmap exif


    【解决方案1】:

    您现在可以使用 Glide 执行此操作。请参阅此处的“后台线程”部分:

    https://bumptech.github.io/glide/doc/getting-started.html

    Bitmap bitmap = Glide.with(context).asBitmap().load(new File(fileName)).skipMemoryCache(true).submit().get();

    Glide 考虑了 EXIF。您需要在后台线程上加载它。我使用的是 Glide 4.9.0

    【讨论】:

      【解决方案2】:

      有没有办法让 BitmapFactory.decodeFile() 尊重 EXIF 并生成正确的位图?

      不,抱歉。

      处理此问题的推荐模式是什么?

      使用支持库的ExifInterface 来确定所需的方向。然后,根据您对Bitmap 的使用情况,旋转视图(例如ImageView)或旋转BitmapThis sample project 说明了这两种方法,尽管我使用了一组单独的 EXIF 代码,因为支持库的 ExifInterface 不支持我在该示例中使用的某些内容。

      【讨论】:

      • Use the support library's ExifInterface:在您的示例中,您使用it.sephiroth.android.library.exif2 库。对于这种特定情况,与android.media.ExifInterface 相比,它有什么优势吗?如果这是由于 stock ExifInterface 的某些实现存在错误,它是否仍然适用于 API >= 14?或者对于这些较新的 API android.media.ExifInterface 应该没问题?
      • @AlexanderAbakumov:我想写出修改后的位图。这需要删除缩略图并更新 EXIF 方向标签。 The support library's ExifInterface 不提供 removeCompressedThumbnail() 方法。如果您不保存修改后的位图,这应该不是问题。
      • @AlexanderAbakumov:在 Android 7.0 之前,平台的 ExifInterface has a significant security flawUse the support library's ExifInterface 或其他基于 Java 的 EXIF 实现(例如我在该示例中的代码)。
      【解决方案3】:

      事实证明,解决最初的问题变成了一系列新问题。这是供更多读者阅读的完整教程。

      首先,作为@CommonsWare pointed in his answer,我们必须使用EXIF方向标签手动处理方向。为了避免错误/安全缺陷android.media.ExifInterface 以及第 3 方依赖项,最好的选择似乎是一个新的 com.android.support:exifinterface 库,我们将其添加到 build.gradle 为:

      dependencies {
          compile 'com.android.support:exifinterface:26.0.0'
      }
      

      但是对我来说,它导致了 Gradle 同步错误,原因是在 Google 存储库中找不到这个特定的最新版本的库,尽管根据Support Library Packages 页面,这个版本是最新的。

      经过一个小时的试用,我发现 jcenter() 存储库还不够,通过添加 Google Maven 存储库修复了 Gradle 同步:

      allprojects {
          repositories {
              jcenter()
              maven {
                  url 'https://maven.google.com'
                  // Alternative URL is 'https://dl.google.com/dl/android/maven2/'
              }
          }
      }
      

      好的,现在我们可以使用android.support.media.ExifInterface了。

      下一个令人失望的是,存储在 EXIF 标签中的宽度和高度也不尊重方向,即对于以纵向模式拍摄的图像,您获得的宽度和高度与使用 BitmapFactory.decodeFile() 创建的 Bitmap 返回的宽度和高度相同。所以唯一的方法是手动查看 EXIF ExifInterface.TAG_ORIENTATION 标签,如果它说图像旋转了 90 度或 270 度 - 交换宽度和高度:

      ExifInterface exif = new ExifInterface(fileNameFull);
      orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
      
      BitmapFactory.Options optsForBounds = new BitmapFactory.Options();
      optsForBounds.inJustDecodeBounds = true;
      BitmapFactory.decodeFile(fileName, optsForBounds);
      
      int width = optsForBounds.outWidth, height = optsForBounds.outHeight;
      if (orientation == ExifInterface.ORIENTATION_ROTATE_90 || orientation == ExifInterface.ORIENTATION_ROTATE_270)
      {
          int temp = width;
          //noinspection SuspiciousNameCombination
          width = height;
          height = temp;
      }
      

      我不确定这种方法和代码是否涵盖 100% 的情况,因此如果您在各种设备/图像源中遇到任何问题 - 请随时发表评论。

      总的来说,处理 EXIF 似乎不是一个愉快的经历。即使是大公司对 EXIF 标准的实施似乎对细微差别的处理方式也大相径庭。见详细分析here

      【讨论】:

        【解决方案4】:
        Bitmap = getThumb(file, dimensionPixelSize);
        
        public Bitmap getThumb(String file, float thumbSize) throws FileNotFoundException, IOException, OutOfMemoryError {
        
            ExifInterface exif = new ExifInterface(file);
            int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
        
            mOptions.inSampleSize = getSample(file, thumbSize);
            mOptions.inJustDecodeBounds = false;
        
            Bitmap bitmap = BitmapFactory.decodeFile(file, mOptions);
        
            if (orientation == ExifInterface.ORIENTATION_ROTATE_90 || orientation == ExifInterface.ORIENTATION_ROTATE_270) {
                return rotateBitmap(bitmap, 90);
            } else {
                return bitmap;
            }
        
        }
        
        public int getSample(String file, float thumbSize) throws FileNotFoundException, IOException, OutOfMemoryError {
            mOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(file, mOptions);
            int size = Math.round(Math.max(mOptions.outWidth, mOptions.outHeight) / thumbSize);
            if (size % 2 != 0) {
                size--;
            }
            return size;
        }
        
        public Bitmap rotateBitmap(Bitmap source, float angle) {
             Matrix matrix = new Matrix();
             matrix.postRotate(angle);
             return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
        }
        

        【讨论】:

          猜你喜欢
          • 2013-07-30
          • 2014-11-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-12-05
          • 1970-01-01
          • 2013-08-18
          相关资源
          最近更新 更多