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

时间:2017-07-05 13:47:35

标签: android exif

在我们的应用中,用户已经使用(大致)此代码上传了数百万张图片:

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

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

4 个答案:

答案 0 :(得分:2)

  

问题是压缩位图时图像Exif数据丢失了

Bitmap中阅读时,EXIF数据会丢失。 Bitmap没有EXIF标记。

  

Exif数据在上传到服务器后如何保留在图像中?

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

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

相反,如果转换为70%-JPEG是某种数据规范化方法,那么在服务器上执行此操作,您将拥有更多CPU功率,更多磁盘空间,更多RAM和持续供电。 / p>

答案 1 :(得分:2)

我的解决方案:

@amuttsch @CommonsWare 建议时,我:

  1. 将缩放/压缩的位图保存到临时文件
  2. 将exif从原始文件复制到临时文件
  3. 将临时文件转换为字节数组并将其发送到上传
  4. ..然后我发现服务器在生成图像变体时再次剥离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 :(得分:0)

来源: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将引导您朝着正确的方向前进。

答案 3 :(得分:0)

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

相关问题