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

时间:2021-03-12 22:40:37

In my tests, a Bitmap created by BitmapFactory.decodeFile() doesn't respect EXIF header.

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

For example, with portrait images taken by a devices that doesn't rotate actual pixel data depending on camera orientation but rather stores it in EXIF header, when I'm calling Bitmap.getWidth() and Bitmap.getHeight(), they return incorrect values (height for width and vice versa).

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

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

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

If not, what is the recommended pattern to handle this issue?

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

Without an advice from experienced Android devs, the only way I see is to pre-process taken images (load, rotate according to EXIF, and save). But apart from the huge processing overhead, this might cause OutOfMemoryExceptions for large camera resolutions (in cases when you can't reduce the quality by using BitmapFactory.Options.inSampleSize to load downscaled images).

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

2 个解决方案

#1


1  

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

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

No, sorry.

what is the recommended pattern to handle this issue?

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

Use the support library's ExifInterface to determine the desired orientation. Then, depending upon your use of the Bitmap, either rotate the view (e.g., ImageView) or rotate the Bitmap. This sample project illustrates both approaches, though I use a separate set of EXIF code, as some things that I use in that sample are not supported by the support library's ExifInterface.

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

#2


2  

As it turned out, resolving the initial issue turned into the set of new ones. Here is the complete tutorial for further readers.

事实证明,解决初始问题变成了一组新问题。这是针对更多读者的完整教程。

First of all, as @CommonsWare pointed in his answer, we have to handle orientation manually using EXIF orientation tag. To avoid buggy/security flawed android.media.ExifInterface as well as 3rd party dependencies, the best option seems to be a new com.android.support:exifinterface library, which we add to build.gradle as:

首先,正如@CommonsWare在他的回答中指出的那样,我们必须使用EXIF方向标记手动处理方向。为了避免错误/安全性有缺陷的android.media.ExifInterface以及第三方依赖,最好的选择似乎是一个新的com.android.support:exifinterface库,我们将它添加到build.gradle中:

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

However for me, it led to a Gradle sync error caused by that this specific newest version of the library is not found in Google repositories, though this version is the latest according to the Support Library Packages page.

但是对我来说,它导致Gradle同步错误,因为在Google存储库中找不到此特定最新版本的库,但根据支持库包页面,此版本是最新版本。

After an hour of trials, I figured out that jcenter() repository is not enough and Gradle sync was fixed by adding Google Maven repository:

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

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

Ok, now we're able to use android.support.media.ExifInterface.

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

The next disappointment was that width and height stored in EXIF tags also do not respect orientation, i.e. for images taken in portrait mode you get the same width and height that are returned by a Bitmap created with BitmapFactory.decodeFile(). So the only way is to manually look at the EXIF ExifInterface.TAG_ORIENTATION tag and if it says that image is rotated 90 or 270 degree - swap width and height:

下一个令人失望的是EXIF标签中存储的宽度和高度也不考虑方向,即对于以纵向模式拍摄的图像,您获得与使用BitmapFactory.decodeFile()创建的位图返回的相同宽度和高度。因此,唯一的方法是手动查看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;
}

I'm not sure if this approach and code covers 100% of cases, so if you encounter any issues across various devices/image sources - feel free to comment.

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

In general, dealing with EXIF doesn't seem to be a pleasant experience. Implementations of the EXIF standard even from big companies seem to treat nuances very differently. See extensive analysis here.

一般来说,处理EXIF似乎并不是一种愉快的体验。即使是来自大公司的EXIF标准的实施似乎也会以不同的方式对待细微差别。请参见此处的广泛分析

#1


1  

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

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

No, sorry.

what is the recommended pattern to handle this issue?

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

Use the support library's ExifInterface to determine the desired orientation. Then, depending upon your use of the Bitmap, either rotate the view (e.g., ImageView) or rotate the Bitmap. This sample project illustrates both approaches, though I use a separate set of EXIF code, as some things that I use in that sample are not supported by the support library's ExifInterface.

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

#2


2  

As it turned out, resolving the initial issue turned into the set of new ones. Here is the complete tutorial for further readers.

事实证明,解决初始问题变成了一组新问题。这是针对更多读者的完整教程。

First of all, as @CommonsWare pointed in his answer, we have to handle orientation manually using EXIF orientation tag. To avoid buggy/security flawed android.media.ExifInterface as well as 3rd party dependencies, the best option seems to be a new com.android.support:exifinterface library, which we add to build.gradle as:

首先,正如@CommonsWare在他的回答中指出的那样,我们必须使用EXIF方向标记手动处理方向。为了避免错误/安全性有缺陷的android.media.ExifInterface以及第三方依赖,最好的选择似乎是一个新的com.android.support:exifinterface库,我们将它添加到build.gradle中:

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

However for me, it led to a Gradle sync error caused by that this specific newest version of the library is not found in Google repositories, though this version is the latest according to the Support Library Packages page.

但是对我来说,它导致Gradle同步错误,因为在Google存储库中找不到此特定最新版本的库,但根据支持库包页面,此版本是最新版本。

After an hour of trials, I figured out that jcenter() repository is not enough and Gradle sync was fixed by adding Google Maven repository:

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

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

Ok, now we're able to use android.support.media.ExifInterface.

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

The next disappointment was that width and height stored in EXIF tags also do not respect orientation, i.e. for images taken in portrait mode you get the same width and height that are returned by a Bitmap created with BitmapFactory.decodeFile(). So the only way is to manually look at the EXIF ExifInterface.TAG_ORIENTATION tag and if it says that image is rotated 90 or 270 degree - swap width and height:

下一个令人失望的是EXIF标签中存储的宽度和高度也不考虑方向,即对于以纵向模式拍摄的图像,您获得与使用BitmapFactory.decodeFile()创建的位图返回的相同宽度和高度。因此,唯一的方法是手动查看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;
}

I'm not sure if this approach and code covers 100% of cases, so if you encounter any issues across various devices/image sources - feel free to comment.

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

In general, dealing with EXIF doesn't seem to be a pleasant experience. Implementations of the EXIF standard even from big companies seem to treat nuances very differently. See extensive analysis here.

一般来说,处理EXIF似乎并不是一种愉快的体验。即使是来自大公司的EXIF标准的实施似乎也会以不同的方式对待细微差别。请参见此处的广泛分析