安卓图片改变尺寸并保留EXIF数据(方向,旋转等)

60
如果你的Android应用程序使用设备相机拍摄照片,然后调整其大小(这非常常见,以减小上传文件的大小),你可能没有意识到此调整大小操作将 剥离 Exif 元数据。

这可能会引起问题,特别是如果依赖"方向"标签来正确显示图片的设备出现在问题。

不同的Android设备以不同的方式处理相机/图像旋转 - 我一直信任我的老 Nexus One 立即捕获后旋转图像,因此当查看文件时,原生内容始终处于"正立"状态。
但是,其他设备(尤其是在我的测试中的三星手机)不会旋转图像文件的内容-而是设置Exif "方向"标签。每当以后显示该图像时,相关的图像代码应检测"标签"的存在,并适当旋转该图像。但是,如果您对图像进行了任何位图处理并将其保存为新文件,则所有Exif数据都会丢失。
除了方向数据之外,您还可能会丢失其他有价值的元数据,例如制造/型号等。

这使我困惑了几周(在手机画廊中显示的图像是直立的,但抵达我的服务器后具有错误的方向和明显的元数据)。我在此添加自问来帮助其他人。这篇博客文章非常有用:

Android重新调整大小图像而不丢失EXIF信息

5个回答

58
据我所知,似乎没有自动持久化元数据的机制,甚至无法快照并批量传输它们。相反,您必须显式地检查特定属性,并使用ExifInterface将其复制到新的图像文件中。 http://developer.android.com/reference/android/media/ExifInterface.html 因此,类似以下代码:
ExifInterface oldExif = new ExifInterface(oldImagePath);
String exifOrientation = oldExif.getAttribute(ExifInterface.TAG_ORIENTATION);

if (exifOrientation != null) {
   ExifInterface newExif = new ExifInterface(imagePath);
   newExif.setAttribute(ExifInterface.TAG_ORIENTATION, exifOrientation);
   newExif.saveAttributes();
}

你是我的英雄。 - Álysson Alexandre

42

对于懒惰的人,这是一个可重复使用的函数:

public static void copyExif(String oldPath, String newPath) throws IOException
{
    ExifInterface oldExif = new ExifInterface(oldPath);

    String[] attributes = new String[]
    {
            ExifInterface.TAG_APERTURE,
            ExifInterface.TAG_DATETIME,
            ExifInterface.TAG_DATETIME_DIGITIZED,
            ExifInterface.TAG_EXPOSURE_TIME,
            ExifInterface.TAG_FLASH,
            ExifInterface.TAG_FOCAL_LENGTH,
            ExifInterface.TAG_GPS_ALTITUDE,
            ExifInterface.TAG_GPS_ALTITUDE_REF,
            ExifInterface.TAG_GPS_DATESTAMP,
            ExifInterface.TAG_GPS_LATITUDE,
            ExifInterface.TAG_GPS_LATITUDE_REF,
            ExifInterface.TAG_GPS_LONGITUDE,
            ExifInterface.TAG_GPS_LONGITUDE_REF,
            ExifInterface.TAG_GPS_PROCESSING_METHOD,
            ExifInterface.TAG_GPS_TIMESTAMP,
            ExifInterface.TAG_IMAGE_LENGTH,
            ExifInterface.TAG_IMAGE_WIDTH,
            ExifInterface.TAG_ISO,
            ExifInterface.TAG_MAKE,
            ExifInterface.TAG_MODEL,
            ExifInterface.TAG_ORIENTATION,
            ExifInterface.TAG_SUBSEC_TIME,
            ExifInterface.TAG_SUBSEC_TIME_DIG,
            ExifInterface.TAG_SUBSEC_TIME_ORIG,
            ExifInterface.TAG_WHITE_BALANCE
    };

    ExifInterface newExif = new ExifInterface(newPath);
    for (int i = 0; i < attributes.length; i++)
    {
        String value = oldExif.getAttribute(attributes[i]);
        if (value != null)
            newExif.setAttribute(attributes[i], value);
    }
    newExif.saveAttributes();
}

13
如果调整大小,请记得更新或不复制TAG_IMAGE_LENGTH和TAG_IMAGE_WIDTH。 - Desmond Lua

21

正如其他人所说,您必须将原始图像的Exif数据复制到最终调整大小的图像中。通常情况下,Sanselan Android库最适合此操作。根据Android操作系统版本不同,ExifInterface有时会破坏Exif数据。

此外,ExifInterface还仅处理有限数量的Exif标记,即仅处理它“知道”的标记。而另一方面,Sanselan将保留所有Exif标记和标记注释。

这里是一篇博客文章,展示了如何使用Sanselan复制图像数据:

使用Sanselan复制Exif元数据

顺便提一下,对于Android平台上的图像,我也倾向于旋转图像并删除Orientation Exiftag标记。例如,在一个搭载Android 4.03的Nexus S手机上,相机设置了Exifmetadata中的方向标记,但Webview忽略该信息并以不正确的方式显示图像。不幸的是,旋转实际图像数据并删除Exiforientation标记是让每个程序都正确显示图像的唯一方法。


谢谢Theo,我很感激你的回答并已经接受了它。你有更多关于“根据Android操作系统版本,ExifInterface有时会破坏EXIF数据”的信息链接吗? - Mike Repass
这是基于我的经验,而且我不是第一个发现 ExifInterface 有 bug 的人[1]。根据接下来几天我有多少时间,我可能会整理一些具有说明性的案例并回报。[1] http://mail-archives.apache.org/mod_mbox/commons-issues/201106.mbox/%3C1082763420.30517.1308779087690.JavaMail.tomcat@hel.zones.apache.org%3E - Theo
@Theo,我已经测试了你的代码,它似乎可以复制某些设备和图像的大部分/全部属性。不太好复制的属性示例包括:ISOSpeedRatings、FocalLength、FNumber。你能解释一下为什么吗?我在一个安卓4.4.2的Nexus 4设备上进行了测试,并比较了原始文件和新文件上创建的ExifInterface。 - android developer

1

已经是2019年了,但是仍然没有比@prom85, Mike RepassTheo提出的答案更好的了。

2016年,Android团队推出了ExifInterface Support Library。如果您想在不同的Android版本之间拥有一致的行为,则可以选择此选项。我最终创建了一个标签子集ExifInterface#EXIF_TAGS源代码),并遍历此子集以从输入文件中提取元数据并将其设置为输出。如果您需要复制所有标签,则建议您不要这样做!某些标签的值仍然需要相应地更新(例如TAG_IMAGE_LENGTHTAG_IMAGE_WIDTH)。就个人而言,我一直在问为什么我们需要首先保留所有元数据(因为它们在不同设备和使用的相机之间会有不同),我们意识到需要保留GPS位置和日期/时间数据。

1
为什么不修改ExifInterface源码并添加自己的实现以支持批量读取/写入,而不必逐个指定标签。以下是我将要执行的代码片段。
添加新方法以公开内部属性:
public HashMap<String, ExifAttribute>[] getAllAttributes() {
    return mAttributes;
}

添加设置所有属性的新方法:

public void setAttributes(HashMap<String, ExifAttribute>[] attributes) {
    for (int i = 0 ; i < EXIF_TAGS.length; ++i) {
        mAttributes[i] = attributes[i];
    }
}

然后像这样使用它来保留Exif并保存到另一个文件。
// Grab all the original exif attributes from an image file and save to memory or wherever
let attributes = ExifInterface2(sourceImagePath).attributes

// Later on you can just copy those attributes to another image
ExifInterface2(destImagePath)
    .setAttributes(attributes)
    .saveAttributes();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接