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

Android: Uploading image without losing Exif data

在我们的应用程序中,用户多年来(大致)使用以下代码上传了数百万张图片:

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

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

图片上传到服务器时如何保留图片中的Exif数据?

不是重复的: 为了更深入地阐明,我尝试使用建议的 duplicate question 通过使用此方法:

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

.. 但在 newExif.saveAttributes(); 之后出现异常 java.io.IOException: ExifInterface does not support saving attributes for the current input.,因为我正在尝试将属性保存到 InputStream。我还能怎么做?

来源:

回答者:https://whosebug.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.

The problem is that the image Exif data is lost when compressing the bitmap

读取 Bitmap 时 EXIF 数据丢失。 Bitmap 没有 EXIF 标签。

How can Exif data be retained in the images when they'er uploaded to the server?

停止阅读 Bitmap。只需按原样上传 postFilePath 的内容。它将包含它包含的任何 EXIF 标签。

我的假设是,您正在阅读 Bitmap,希望以 70% JPEG 质量再次保存它会带来有意义的带宽节省。我怀疑您没有节省太多,并且在某些情况下您可能会增加带宽(例如,postFilePath 指向 PNG)。您的成本是 CPU 时间、增加的 OutOfMemoryError 风险以及丢失 EXIF 标签。

相反,如果转换为 70%-JPEG 是某种数据规范化方法,请在服务器上执行此操作,那里有更多 CPU 功能,更多磁盘 space、更多 RAM 和持续供电。

我的解决方案:

正如 @amuttsch@CommonsWare 所建议的,我:

  1. 将 scaled/compressed 位图保存到临时文件
  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();
    }
}

读取文件():

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

您只需要创建一个新的 OutputStream 来保存 Exif 信息。无需创建新文件。