是否可以让 BitmapFactory.decodeFile() 尊重 EXIF?

Is it possible to make BitmapFactory.decodeFile() respect EXIF?

在我的测试中,BitmapFactory.decodeFile() 创建的 Bitmap 不支持 EXIF header。

例如,当我调用 Bitmap.getWidth()Bitmap.getHeight(),它们 return 不正确的值(高度为宽度,反之亦然)。

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

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

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

Is there a way to make BitmapFactory.decodeFile() respect EXIF and produce a correct Bitmap?

不,抱歉。

what is the recommended pattern to handle this issue?

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

不支持我在该示例中使用的某些内容

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

首先,和一样,我们必须使用EXIF方向标签手动处理方向。为了避免 buggy/security 有缺陷的 android.media.ExifInterface 以及第三方依赖项,最好的选择似乎是一个新的 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 标签中的宽度和高度也不符合方向,即对于以纵向模式拍摄的图像,您获得的宽度和高度与使用 [创建的 Bitmap 返回的宽度和高度相同=19=]。所以唯一的方法是手动查看 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% 的案例,所以如果您在各种 devices/image 来源中遇到任何问题 - 请随时发表评论。

总的来说,处理 EXIF 似乎不是一种愉快的经历。即使是来自大公司的 EXIF 标准的实施似乎也非常不同地对待细微差别。查看广泛的分析 here

您现在可以使用 Glide 执行此操作。请参阅此处的 "Background Threads" 部分:

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

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

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

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);
}