2017-07-05 31 views
1

在我們的應用程序的用戶已經上傳百萬計的圖像多年來(大約)驗證碼:安卓:上傳圖片,而無需使用丟失的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。我還能怎麼做?

+1

的可能的複製[如何保存的Exif數據中之後的Android位圖壓縮](https://stackoverflow.com/questions/23762133/how-to-save-exif-data-位圖壓縮後在Android) – amuttsch

+0

我見過這個。經過建議之後,但由於我寫了'ExifInterface'只能保存到圖像,這是我的問題,所以它不是重複的。 – Ambran

+1

我沒有看到你的問題。爲原始文件和壓縮文件創建一個'ExifInterface'(從輸出流創建一個新的'Bitmap')並使用'exifComp.setAttribute(TAG _...,exifOrig(TAG _...));'並保存它用'exifComp.save()'。之後,從壓縮文件中獲取輸出流。 – amuttsch

回答

0

我的解決辦法:

由於@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(); 
    } 
} 
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將引導你在一個正確的方向。

+0

該鏈接返回一個404 ,sanselan現在被稱爲Apache Commons Imaging,請參閱https://commons.apache.org/proper/commons-imaging/ – amuttsch

1

的問題是,壓縮位圖在Bitmap讀取時

的EXIF數據丟失當圖像Exif數據丟失。 A Bitmap沒有EXIF標籤。

Exif數據在上傳到服務器時如何保留在圖像中?

停止閱讀Bitmap。只需按原樣上傳postFilePath的內容即可。它將包含它包含的任何EXIF標籤。

我的假設是,您正在閱讀Bitmap,希望以70%的JPEG質量再次保存會帶來有意義的帶寬節省。我懷疑你沒有太多節省,並且在某些情況下你可能會增加帶寬(例如,postFilePath指向PNG)。您的成本是一小部分CPU時間,增加了一個OutOfMemoryError的風險,以及您的EXIF標籤丟失。

如果轉換爲70%-JPEG是某種數據規範化方法,那麼在服務器上執行該操作,即可以獲得更多CPU功率,更多磁盤空間,更多RAM和連續功率。

+0

我明白你的意思。正如我所說的代碼是3-4歲,已經上傳了數百萬的圖像到服務器。該代碼在創建位圖之前縮放圖像,這就是爲什麼它們不能直接上傳。我只是沒有發佈縮放代碼,因爲它很長,我不認爲它是相關的。我將不得不重新審視整個事情。 – Ambran

+1

@Ambran:啊,好吧,如果你也在縮放圖像,那麼很容易在帶寬消耗方面產生更大的差異。但是,在這種情況下,我的猜測是,您不需要使用'ByteArrayOutputStream',而是需要將縮放後的JPEG文件寫入文件,以便從原始文件重新應用EXIF標頭。然後,從該文件上傳。 – CommonsWare

+0

是的,這就是@amuttsch也建議這樣的路要走。我現在正在處理這個問題。 – Ambran