是否可以让 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 旋转并保存)。但除了巨大的处理开销之外,对于大相机分辨率,这可能会导致 OutOfMemoryException
s(在无法通过使用 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
)或旋转 Bitmap
。 This 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);
}
在我的测试中,BitmapFactory.decodeFile()
创建的 Bitmap
不支持 EXIF header。
例如,当我调用 Bitmap.getWidth()
和Bitmap.getHeight()
,它们 return 不正确的值(高度为宽度,反之亦然)。
有没有办法让 BitmapFactory.decodeFile()
尊重 EXIF 并生成正确的 Bitmap
?
如果不是,处理此问题的推荐模式是什么?
没有经验丰富的 Android 开发人员的建议,我看到的唯一方法是 pre-process 拍摄的图像(加载、根据 EXIF 旋转并保存)。但除了巨大的处理开销之外,对于大相机分辨率,这可能会导致 OutOfMemoryException
s(在无法通过使用 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
)或旋转 Bitmap
。 This sample project 说明了这两种方法,尽管我使用了一组单独的 EXIF 代码,因为支持库的 ExifInterface
.
事实证明,解决最初的问题变成了一系列新问题。这是供更多读者使用的完整教程。
首先,和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);
}