mirror of
https://github.com/apache/cordova-android.git
synced 2025-02-27 04:42:51 +08:00
Fix for issue #141: EXIF data stripped from captured photos in android
In order to fix this issue I needed to read the EXIF data. Save it to a temporary object then after the bitmap is compressed I open the file and write the saved EXIF data. Supports the following EXIF fields if they are set in your image: APERTURE DATETIME EXPOSURE_TIME FLASH FOCAL_LENGTH GPS_ALTITUDE GPS_ALTITUDE_REF GPS_DATESTAMP GPS_LATITUDE GPS_LATITUDE_REF GPS_LONGITUDE GPS_LONGITUDE_REF GPS_PROCESSING_METHOD GPS_TIMESTAMP ISO MAKE MODEL ORIENTATION WHITE_BALANCE
This commit is contained in:
parent
2e9cbdf38d
commit
0297807bd0
@ -48,7 +48,9 @@ public class CameraLauncher extends Plugin {
|
|||||||
private int mQuality; // Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
|
private int mQuality; // Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
|
||||||
private int targetWidth; // desired width of the image
|
private int targetWidth; // desired width of the image
|
||||||
private int targetHeight; // desired height of the image
|
private int targetHeight; // desired height of the image
|
||||||
private Uri imageUri; // Uri of captured image
|
private Uri imageUri;
|
||||||
|
private int encodingType;
|
||||||
|
// Uri of captured image
|
||||||
public String callbackId;
|
public String callbackId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -86,9 +88,9 @@ public class CameraLauncher extends Plugin {
|
|||||||
if (args.length() > 4) {
|
if (args.length() > 4) {
|
||||||
this.targetHeight = args.getInt(4);
|
this.targetHeight = args.getInt(4);
|
||||||
}
|
}
|
||||||
int encodingType = JPEG;
|
this.encodingType = JPEG;
|
||||||
if (args.length() > 5) {
|
if (args.length() > 5) {
|
||||||
encodingType = args.getInt(5);
|
this.encodingType = args.getInt(5);
|
||||||
}
|
}
|
||||||
if (srcType == CAMERA) {
|
if (srcType == CAMERA) {
|
||||||
this.takePicture(args.getInt(0), destType, encodingType);
|
this.takePicture(args.getInt(0), destType, encodingType);
|
||||||
@ -238,6 +240,13 @@ public class CameraLauncher extends Plugin {
|
|||||||
// If image available
|
// If image available
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
try {
|
try {
|
||||||
|
// Create an ExifHelper to save the exif data that is lost during compression
|
||||||
|
ExifHelper exif = new ExifHelper();
|
||||||
|
if (this.encodingType == JPEG) {
|
||||||
|
exif.createInFile(DirectoryManager.getTempDirectoryPath(ctx) + "/Pic.jpg");
|
||||||
|
exif.readExifData();
|
||||||
|
}
|
||||||
|
|
||||||
// Read in bitmap of captured image
|
// Read in bitmap of captured image
|
||||||
Bitmap bitmap;
|
Bitmap bitmap;
|
||||||
try {
|
try {
|
||||||
@ -280,6 +289,12 @@ public class CameraLauncher extends Plugin {
|
|||||||
bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os);
|
bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os);
|
||||||
os.close();
|
os.close();
|
||||||
|
|
||||||
|
// Restore exif data to file
|
||||||
|
if (this.encodingType == JPEG) {
|
||||||
|
exif.createOutFile(FileUtils.getRealPathFromURI(uri, this.ctx));
|
||||||
|
exif.writeExifData();
|
||||||
|
}
|
||||||
|
|
||||||
// Send Uri back to JavaScript for viewing image
|
// Send Uri back to JavaScript for viewing image
|
||||||
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
|
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
|
||||||
}
|
}
|
||||||
|
@ -18,12 +18,10 @@ import org.json.JSONObject;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.media.MediaPlayer;
|
import android.media.MediaPlayer;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Environment;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.phonegap.api.Plugin;
|
import com.phonegap.api.Plugin;
|
||||||
@ -31,7 +29,6 @@ import com.phonegap.api.PluginResult;
|
|||||||
|
|
||||||
public class Capture extends Plugin {
|
public class Capture extends Plugin {
|
||||||
|
|
||||||
private static final String _DATA = "_data"; // The column name where the file path is stored
|
|
||||||
private static final int CAPTURE_AUDIO = 0; // Constant for capture audio
|
private static final int CAPTURE_AUDIO = 0; // Constant for capture audio
|
||||||
private static final int CAPTURE_IMAGE = 1; // Constant for capture image
|
private static final int CAPTURE_IMAGE = 1; // Constant for capture image
|
||||||
private static final int CAPTURE_VIDEO = 2; // Constant for capture video
|
private static final int CAPTURE_VIDEO = 2; // Constant for capture video
|
||||||
@ -225,6 +222,11 @@ public class Capture extends Plugin {
|
|||||||
// It crashes in the emulator and on my phone with a null pointer exception
|
// It crashes in the emulator and on my phone with a null pointer exception
|
||||||
// To work around it I had to grab the code from CameraLauncher.java
|
// To work around it I had to grab the code from CameraLauncher.java
|
||||||
try {
|
try {
|
||||||
|
// Create an ExifHelper to save the exif data that is lost during compression
|
||||||
|
ExifHelper exif = new ExifHelper();
|
||||||
|
exif.createInFile(DirectoryManager.getTempDirectoryPath(ctx) + "/Capture.jpg");
|
||||||
|
exif.readExifData();
|
||||||
|
|
||||||
// Read in bitmap of captured image
|
// Read in bitmap of captured image
|
||||||
Bitmap bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri);
|
Bitmap bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri);
|
||||||
|
|
||||||
@ -255,6 +257,10 @@ public class Capture extends Plugin {
|
|||||||
bitmap = null;
|
bitmap = null;
|
||||||
System.gc();
|
System.gc();
|
||||||
|
|
||||||
|
// Restore exif data to file
|
||||||
|
exif.createOutFile(FileUtils.getRealPathFromURI(uri, this.ctx));
|
||||||
|
exif.writeExifData();
|
||||||
|
|
||||||
// Add image to results
|
// Add image to results
|
||||||
results.put(createMediaFile(uri));
|
results.put(createMediaFile(uri));
|
||||||
|
|
||||||
@ -313,10 +319,10 @@ public class Capture extends Plugin {
|
|||||||
*
|
*
|
||||||
* @param data the Uri of the audio/image/video
|
* @param data the Uri of the audio/image/video
|
||||||
* @return a JSONObject that represents a File
|
* @return a JSONObject that represents a File
|
||||||
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
private JSONObject createMediaFile(Uri data){
|
private JSONObject createMediaFile(Uri data){
|
||||||
File fp = new File(getRealPathFromURI(data));
|
File fp = new File(FileUtils.getRealPathFromURI(data, this.ctx));
|
||||||
|
|
||||||
JSONObject obj = new JSONObject();
|
JSONObject obj = new JSONObject();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -334,20 +340,6 @@ public class Capture extends Plugin {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Queries the media store to find out what the file path is for the Uri we supply
|
|
||||||
*
|
|
||||||
* @param contentUri the Uri of the audio/image/video
|
|
||||||
* @return the full path to the file
|
|
||||||
*/
|
|
||||||
private String getRealPathFromURI(Uri contentUri) {
|
|
||||||
String[] proj = { _DATA };
|
|
||||||
Cursor cursor = this.ctx.managedQuery(contentUri, proj, null, null, null);
|
|
||||||
int column_index = cursor.getColumnIndexOrThrow(_DATA);
|
|
||||||
cursor.moveToFirst();
|
|
||||||
return cursor.getString(column_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send error message to JavaScript.
|
* Send error message to JavaScript.
|
||||||
*
|
*
|
||||||
|
@ -914,13 +914,13 @@ public class DroidGap extends PhonegapActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Polling for JavaScript messages
|
// Polling for JavaScript messages
|
||||||
else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) {
|
else if (reqOk && defaultValue.equals("gap_poll:")) {
|
||||||
String r = callbackServer.getJavascript();
|
String r = callbackServer.getJavascript();
|
||||||
result.confirm(r);
|
result.confirm(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calling into CallbackServer
|
// Calling into CallbackServer
|
||||||
else if (reqOk && defaultValue != null && defaultValue.equals("gap_callbackServer:")) {
|
else if (reqOk && defaultValue.equals("gap_callbackServer:")) {
|
||||||
String r = "";
|
String r = "";
|
||||||
if (message.equals("usePolling")) {
|
if (message.equals("usePolling")) {
|
||||||
r = ""+callbackServer.usePolling();
|
r = ""+callbackServer.usePolling();
|
||||||
@ -939,7 +939,7 @@ public class DroidGap extends PhonegapActivity {
|
|||||||
|
|
||||||
// PhoneGap JS has initialized, so show webview
|
// PhoneGap JS has initialized, so show webview
|
||||||
// (This solves white flash seen when rendering HTML)
|
// (This solves white flash seen when rendering HTML)
|
||||||
else if (reqOk && defaultValue != null && defaultValue.equals("gap_init:")) {
|
else if (reqOk && defaultValue.equals("gap_init:")) {
|
||||||
appView.setVisibility(View.VISIBLE);
|
appView.setVisibility(View.VISIBLE);
|
||||||
ctx.spinnerStop();
|
ctx.spinnerStop();
|
||||||
result.confirm("OK");
|
result.confirm("OK");
|
||||||
|
153
framework/src/com/phonegap/ExifHelper.java
Normal file
153
framework/src/com/phonegap/ExifHelper.java
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* PhoneGap is available under *either* the terms of the modified BSD license *or* the
|
||||||
|
* MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2011, IBM Corporation
|
||||||
|
*/
|
||||||
|
package com.phonegap;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import android.media.ExifInterface;
|
||||||
|
|
||||||
|
public class ExifHelper {
|
||||||
|
private String aperature = null;
|
||||||
|
private String datetime = null;
|
||||||
|
private String exposureTime = null;
|
||||||
|
private String flash = null;
|
||||||
|
private String focalLength = null;
|
||||||
|
private String gpsAltitude = null;
|
||||||
|
private String gpsAltitudeRef = null;
|
||||||
|
private String gpsDateStamp = null;
|
||||||
|
private String gpsLatitude = null;
|
||||||
|
private String gpsLatitudeRef = null;
|
||||||
|
private String gpsLongitude = null;
|
||||||
|
private String gpsLongitudeRef = null;
|
||||||
|
private String gpsProcessingMethod = null;
|
||||||
|
private String gpsTimestamp = null;
|
||||||
|
private String iso = null;
|
||||||
|
private String make = null;
|
||||||
|
private String model = null;
|
||||||
|
private String orientation = null;
|
||||||
|
private String whiteBalance = null;
|
||||||
|
|
||||||
|
private ExifInterface inFile = null;
|
||||||
|
private ExifInterface outFile = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file before it is compressed
|
||||||
|
*
|
||||||
|
* @param filePath
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public void createInFile(String filePath) throws IOException {
|
||||||
|
this.inFile = new ExifInterface(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file after it has been compressed
|
||||||
|
*
|
||||||
|
* @param filePath
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public void createOutFile(String filePath) throws IOException {
|
||||||
|
this.outFile = new ExifInterface(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads all the EXIF data from the input file.
|
||||||
|
*/
|
||||||
|
public void readExifData() {
|
||||||
|
this.aperature = inFile.getAttribute(ExifInterface.TAG_APERTURE);
|
||||||
|
this.datetime = inFile.getAttribute(ExifInterface.TAG_DATETIME);
|
||||||
|
this.exposureTime = inFile.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
|
||||||
|
this.flash = inFile.getAttribute(ExifInterface.TAG_FLASH);
|
||||||
|
this.focalLength = inFile.getAttribute(ExifInterface.TAG_FOCAL_LENGTH);
|
||||||
|
this.gpsAltitude = inFile.getAttribute(ExifInterface.TAG_GPS_ALTITUDE);
|
||||||
|
this.gpsAltitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF);
|
||||||
|
this.gpsDateStamp = inFile.getAttribute(ExifInterface.TAG_GPS_DATESTAMP);
|
||||||
|
this.gpsLatitude = inFile.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
|
||||||
|
this.gpsLatitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
|
||||||
|
this.gpsLongitude = inFile.getAttribute(ExifInterface.TAG_GPS_LONGITUDE);
|
||||||
|
this.gpsLongitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
|
||||||
|
this.gpsProcessingMethod = inFile.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD);
|
||||||
|
this.gpsTimestamp = inFile.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP);
|
||||||
|
this.iso = inFile.getAttribute(ExifInterface.TAG_ISO);
|
||||||
|
this.make = inFile.getAttribute(ExifInterface.TAG_MAKE);
|
||||||
|
this.model = inFile.getAttribute(ExifInterface.TAG_MODEL);
|
||||||
|
this.orientation = inFile.getAttribute(ExifInterface.TAG_ORIENTATION);
|
||||||
|
this.whiteBalance = inFile.getAttribute(ExifInterface.TAG_WHITE_BALANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the previously stored EXIF data to the output file.
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public void writeExifData() throws IOException {
|
||||||
|
// Don't try to write to a null file
|
||||||
|
if (this.outFile == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.aperature != null) {
|
||||||
|
this.outFile.setAttribute(ExifInterface.TAG_APERTURE, this.aperature);
|
||||||
|
}
|
||||||
|
if (this.datetime != null) {
|
||||||
|
this.outFile.setAttribute(ExifInterface.TAG_DATETIME, this.datetime);
|
||||||
|
}
|
||||||
|
if (this.exposureTime != null) {
|
||||||
|
this.outFile.setAttribute(ExifInterface.TAG_EXPOSURE_TIME, this.exposureTime);
|
||||||
|
}
|
||||||
|
if (this.flash != null) {
|
||||||
|
this.outFile.setAttribute(ExifInterface.TAG_FLASH, this.flash);
|
||||||
|
}
|
||||||
|
if (this.focalLength != null) {
|
||||||
|
this.outFile.setAttribute(ExifInterface.TAG_FOCAL_LENGTH, this.focalLength);
|
||||||
|
}
|
||||||
|
if (this.gpsAltitude != null) {
|
||||||
|
this.outFile.setAttribute(ExifInterface.TAG_GPS_ALTITUDE, this.gpsAltitude);
|
||||||
|
}
|
||||||
|
if (this.gpsAltitudeRef != null) {
|
||||||
|
this.outFile.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF, this.gpsAltitudeRef);
|
||||||
|
}
|
||||||
|
if (this.gpsDateStamp != null) {
|
||||||
|
this.outFile.setAttribute(ExifInterface.TAG_GPS_DATESTAMP, this.gpsDateStamp);
|
||||||
|
}
|
||||||
|
if (this.gpsLatitude != null) {
|
||||||
|
this.outFile.setAttribute(ExifInterface.TAG_GPS_LATITUDE, this.gpsLatitude);
|
||||||
|
}
|
||||||
|
if (this.gpsLatitudeRef != null) {
|
||||||
|
this.outFile.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, this.gpsLatitudeRef);
|
||||||
|
}
|
||||||
|
if (this.gpsLongitude != null) {
|
||||||
|
this.outFile.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, this.gpsLongitude);
|
||||||
|
}
|
||||||
|
if (this.gpsLongitudeRef != null) {
|
||||||
|
this.outFile.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, this.gpsLongitudeRef);
|
||||||
|
}
|
||||||
|
if (this.gpsProcessingMethod != null) {
|
||||||
|
this.outFile.setAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD, this.gpsProcessingMethod);
|
||||||
|
}
|
||||||
|
if (this.gpsTimestamp != null) {
|
||||||
|
this.outFile.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, this.gpsTimestamp);
|
||||||
|
}
|
||||||
|
if (this.iso != null) {
|
||||||
|
this.outFile.setAttribute(ExifInterface.TAG_ISO, this.iso);
|
||||||
|
}
|
||||||
|
if (this.make != null) {
|
||||||
|
this.outFile.setAttribute(ExifInterface.TAG_MAKE, this.make);
|
||||||
|
}
|
||||||
|
if (this.model != null) {
|
||||||
|
this.outFile.setAttribute(ExifInterface.TAG_MODEL, this.model);
|
||||||
|
}
|
||||||
|
if (this.orientation != null) {
|
||||||
|
this.outFile.setAttribute(ExifInterface.TAG_ORIENTATION, this.orientation);
|
||||||
|
}
|
||||||
|
if (this.whiteBalance != null) {
|
||||||
|
this.outFile.setAttribute(ExifInterface.TAG_WHITE_BALANCE, this.whiteBalance);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.outFile.saveAttributes();
|
||||||
|
}
|
||||||
|
}
|
@ -25,6 +25,7 @@ import android.provider.MediaStore;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.webkit.MimeTypeMap;
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
|
import com.phonegap.api.PhonegapActivity;
|
||||||
import com.phonegap.api.Plugin;
|
import com.phonegap.api.Plugin;
|
||||||
import com.phonegap.api.PluginResult;
|
import com.phonegap.api.PluginResult;
|
||||||
import com.phonegap.file.EncodingException;
|
import com.phonegap.file.EncodingException;
|
||||||
@ -39,6 +40,7 @@ import com.phonegap.file.TypeMismatchException;
|
|||||||
*/
|
*/
|
||||||
public class FileUtils extends Plugin {
|
public class FileUtils extends Plugin {
|
||||||
private static final String LOG_TAG = "FileUtils";
|
private static final String LOG_TAG = "FileUtils";
|
||||||
|
private static final String _DATA = "_data"; // The column name where the file path is stored
|
||||||
|
|
||||||
public static int NOT_FOUND_ERR = 1;
|
public static int NOT_FOUND_ERR = 1;
|
||||||
public static int SECURITY_ERR = 2;
|
public static int SECURITY_ERR = 2;
|
||||||
@ -988,5 +990,19 @@ public class FileUtils extends Plugin {
|
|||||||
return new FileInputStream(path);
|
return new FileInputStream(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries the media store to find out what the file path is for the Uri we supply
|
||||||
|
*
|
||||||
|
* @param contentUri the Uri of the audio/image/video
|
||||||
|
* @param ctx the current applicaiton context
|
||||||
|
* @return the full path to the file
|
||||||
|
*/
|
||||||
|
protected static String getRealPathFromURI(Uri contentUri, PhonegapActivity ctx) {
|
||||||
|
String[] proj = { _DATA };
|
||||||
|
Cursor cursor = ctx.managedQuery(contentUri, proj, null, null, null);
|
||||||
|
int column_index = cursor.getColumnIndexOrThrow(_DATA);
|
||||||
|
cursor.moveToFirst();
|
||||||
|
return cursor.getString(column_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user