【问题标题】:Android: Uploading image without losing Exif dataAndroid:上传图片而不丢失 Exif 数据
【发布时间】:2017-07-05 13:47:34
【问题描述】:

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

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。我还能怎么做?

【问题讨论】:

  • 我看过这个。遵循建议,但正如我写的ExifInterface 只能保存到图像,这是我的问题,所以它不是重复的。
  • 我没有看到你的问题。为原始文件和压缩文件创建一个ExifInterface(从输出流创建一个新的Bitmap)并使用exifComp.setAttribute(TAG_..., exifOrig(TAG_...)); 并使用exifComp.save() 保存它。然后,从压缩文件中获取输出流。
  • @amuttsch,您的意思是先保存压缩文件,然后将exif属性保存到其中,然后再次从中读取流吗?我可以这样做,但希望避免在此过程中将压缩文件保存到存储中。
  • 您可以从输出流中获取输入流并使用BitmapFactory,参见:stackoverflow.com/questions/29286599/…

标签: android exif


【解决方案1】:

我的解决方案:

正如 @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:啊,好吧,如果您还缩放图像,那么很容易在带宽消耗方面产生更大的差异。不过,在这种情况下,我猜测您需要将缩放的 JPEG 写入文件,而不是使用 ByteArrayOutputStream,因此您可以重新应用原始文件中的 EXIF 标头。然后,从该文件上传。
    • 是的,@amuttsch 也建议这样做,所以这就是要走的路。我现在正在努力。
    【解决方案3】:

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

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

    将文件转换为位图

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

    你也可以指定选项,查看 API documentation

    或者,如果您想将元数据从一个文件交换到另一个文件, sanselan 可能是最好的选择。这将是很多 在处理图像时很有帮助,例如调整大小。

    sample code 将引导您朝着正确的方向前进。

    【讨论】:

    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-27
      • 1970-01-01
      • 2010-09-28
      • 2010-11-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多