Android:上传图片而不丢失Exif数据

5

在我们的应用程序中,用户已经使用以下代码上传了数百万张图片:

BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(postFilePath, bmOptions);
Bitmap roughBitmap = BitmapFactory.decodeFile(postFilePath, bmOptions);

ByteArrayOutputStream stream = new ByteArrayOutputStream();

roughBitmap.compress(Bitmap.CompressFormat.JPEG, 70, stream);
InputStream fis = new ByteArrayInputStream(stream.toByteArray());

int fileSize = stream.toByteArray().length;
conn.setRequestProperty("Content-Length", Integer.toString(fileSize));
conn.setFixedLengthStreamingMode(fileSize);

...

if (fis != null) {
    byte[] buf = new byte[10240];

    int read;

    while ((read = fis.read(buf)) > 0) {
        os.write(buf, 0, read);
        totalBytesRead += read;
        if (uploadProgressListener != null) {
            try {
                uploadProgressListener.onBytesUploaded(read);
            } catch (Exception e) {
                Log.e(e);
            }
        }
    }

    fis.close();
}

最近我们注意到需要保留上传图片的Exif数据。问题在于当压缩位图时,图片的Exif数据就会丢失。我考虑使用ExifInterface从原始文件中提取这些数据:

ExifInterface oldExif = new ExifInterface(postFilePath);
String value = oldExif.getAttribute(ExifInterface.TAG_DATETIME);

...然后将其添加到fis的InputStream中,然后继续上传文件。问题是ExifInterface无法将Exif数据保存到InputStream中。

当图像上传到服务器时,如何保留Exif数据?

这不是重复问题: 为了更深入地澄清,我尝试使用建议的重复问题,使用以下方法:

public static void copyExif(String originalPath, InputStream newStream) throws IOException {

    String[] attributes = new String[]
            {
                    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_MAKE,
                    ExifInterface.TAG_MODEL,
                    ExifInterface.TAG_ORIENTATION,
                    ExifInterface.TAG_SUBSEC_TIME,
                    ExifInterface.TAG_WHITE_BALANCE
            };

    ExifInterface oldExif = new ExifInterface(originalPath);
    ExifInterface newExif = new ExifInterface(newStream);

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

我尝试将属性保存到InputStream中,但在newExif.saveAttributes();之后出现了异常java.io.IOException: ExifInterface不支持为当前输入保存属性。 我该怎么办?


1
可能是如何在Android中压缩位图后保存Exif数据的重复问题。 - amuttsch
我已经看过这个了。按照建议去尝试了,但是正如我所写的,ExifInterface 只能保存到图像中,这就是我的问题所在,因此它不是重复的。 - Ambran
1
我没有看到你的问题。为原始文件和压缩文件都创建一个 ExifInterface(从输出流中创建一个新的 Bitmap),并使用 exifComp.setAttribute(TAG_..., exifOrig(TAG_...));,然后用 exifComp.save() 保存。之后,从压缩文件获取输出流。 - amuttsch
@amuttsch,您的意思是先保存压缩文件,然后将exif属性保存到其中,再从中读取流吗?我可以这样做,但希望在此过程中避免将压缩文件保存到存储中。 - Ambran
1
你可以从一个输出流中获取输入流并使用 BitmapFactory,参见:https://dev59.com/Q4nca4cB1Zd3GeqP6h4D - amuttsch
4个回答

9

我的解决方案:

@amuttsch@CommonsWare所建议,我:

  1. 将缩放/压缩后的位图保存到临时文件中
  2. 将原始文件的Exif复制到临时文件中
  3. 将临时文件转换为字节数组并发送上传

... 然后我发现服务器在生成图像变体时再次剥离了Exif :-P 但这是另一个故事,服务器人员现在正在努力纠正。

主要代码:

...
// Copy original Exif to scaledBitmap
String tempFilePath = getTempFilePath(postFilePath);
try {
    FileOutputStream out = new FileOutputStream(tempFilePath);
    scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 70, out);
    copyExif(postFilePath, tempFilePath);
} catch (Exception e) {
    e.printStackTrace();
}

// Get stream from temp (exif loaded) file
File tempFile = new File(tempFilePath);
byte[] byteFile = readFile(tempFile);
fis = new ByteArrayInputStream(byteFile);

// Remove the temp file
boolean deleted = tempFile.delete();

// Finalize
int fileSize = byteFile.length;
conn.setRequestProperty("Content-Length", Integer.toString(fileSize));
conn.setFixedLengthStreamingMode(fileSize);
...

getTempFilePath():

private String getTempFilePath(String filename) {
    String temp = "_temp";
    int dot = filename.lastIndexOf(".");
    String ext = filename.substring(dot + 1);

    if (dot == -1 || !ext.matches("\\w+")) {
        filename += temp;
    } else {
        filename = filename.substring(0, dot) + temp + "." + ext;
    }

    return filename;
}

copyExif():

public static void copyExif(String originalPath, String newPath) throws IOException {

    String[] attributes = new String[]
            {
                    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_MAKE,
                    ExifInterface.TAG_MODEL,
                    ExifInterface.TAG_ORIENTATION,
                    ExifInterface.TAG_SUBSEC_TIME,
                    ExifInterface.TAG_WHITE_BALANCE
            };

    ExifInterface oldExif = new ExifInterface(originalPath);
    ExifInterface newExif = new ExifInterface(newPath);

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

readFile():

public static byte[] readFile(File file) throws IOException {
    // Open file
    RandomAccessFile f = new RandomAccessFile(file, "r");
    try {
        // Get and check length
        long longlength = f.length();
        int length = (int) longlength;
        if (length != longlength)
            throw new IOException("File size >= 2 GB");
        // Read file and return data
        byte[] data = new byte[length];
        f.readFully(data);
        return data;
    } finally {
        f.close();
    }
}

2
问题在于压缩位图时会丢失图像的Exif数据。
读取Bitmap时会丢失EXIF数据。 Bitmap没有EXIF标签。
上传到服务器时如何保留图像的Exif数据?
停止读取Bitmap。只需按原样上传postFilePath的内容。它将包含任何它包含的EXIF标签。
我假设您正在读取Bitmap,希望以70%的JPEG质量再次保存它,以实现有意义的带宽节省。我怀疑您并没有节省多少,而且在某些情况下(例如,postFilePath指向PNG),您可能会增加带宽。您的成本是一大块CPU时间,增加了OutOfMemoryError的风险,并且丢失了您的EXIF标签。
如果转换为70%的JPEG是某种数据规范化方法,请在服务器上执行该工作,因为您拥有更多的CPU功率,更多的磁盘空间,更多的RAM和持续的电源。

我理解你的意思。就像我所说的那样,这段代码已经存在3-4年,并且已经上传了数百万张图片到服务器上。在创建位图之前,该代码会对图片进行缩放,因此它们不会直接上传。我没有发布缩放代码,因为它太长了,而且我认为它并不相关。我得重新看整个代码。 - Ambran
1
@Ambran:啊,如果你也在缩放图像,那么这很容易导致带宽消耗增加。但是,在这种情况下,我猜你需要将缩放后的JPEG写入文件中,以便可以重新应用原始文件的EXIF头。然后,从该文件上传。 - CommonsWare
是的,@amuttsch也建议了这样做,所以这就是我们要走的路。我正在努力工作。 - Ambran

0
你只需要创建一个新的OutputStream来保留Exif信息,不需要创建一个新文件。

0

来源:https://stackoverflow.com/a/11572752/8252521

回答者:https://stackoverflow.com/users/1592398/code-jaff

Convert the file to bitmap by

Bitmap bi = BitmapFactory.decode(filepath + "DSC00021.jpg");

You can specify options too, look at API documentation

Or if you want to exchange the meta data from one file to another, sanselan will probably be the best choice. This would be much helpful when you manipulating the image, for example re-size.

The sample code will guide you in a right direction.


该链接返回404错误,sanselan现在被称为Apache Commons Imaging,请参见https://commons.apache.org/proper/commons-imaging/。 - amuttsch

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