Skip to content

Commit

Permalink
added location support for android and raised package version
Browse files Browse the repository at this point in the history
  • Loading branch information
Rafael Haußmann committed Aug 23, 2024
1 parent a6f17b3 commit fd1e204
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 74 deletions.
173 changes: 113 additions & 60 deletions android/src/main/java/com/exifmodifier/ExifModifierModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,85 +14,138 @@
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.ReadableMap;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Map;
import java.util.HashMap;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
import java.util.Date;
import java.util.Locale;

public class ExifModifierModule extends ReactContextBaseJavaModule {
public ExifModifierModule(ReactApplicationContext reactContext) {
super(reactContext);
public ExifModifierModule(ReactApplicationContext reactContext) {
super(reactContext);
}

@Override
public String getName() {
return "ExifModifier";
}

@ReactMethod
public void saveImageWithUserComment(String base64ImageData, String userComment, Promise promise) {
try {
Map<String, String> properties = new HashMap<>();

properties.put(ExifInterface.TAG_USER_COMMENT, userComment);
saveImageAndModifyProperties(base64ImageData, properties, promise);
} catch (Exception e) {
promise.reject("E_IMAGE_PROCESSING", e);
}

@Override
public String getName() {
return "ExifModifier";
}

@ReactMethod
public void saveImageWithProperties(String base64ImageData, ReadableMap properties, Promise promise) {
try {
Map<String, String> mappedProperties = new HashMap<>();

if (
properties.hasKey("GPSLatitude") &&
properties.hasKey("GPSLongitude") &&
properties.hasKey("GPSAltitude")
) {
double latitude = Double.parseDouble(properties.getString("GPSLatitude"));
double longitude = Double.parseDouble(properties.getString("GPSLongitude"));
double altitude = Double.parseDouble(properties.getString("GPSAltitude"));

mappedProperties.put(ExifInterface.TAG_GPS_LATITUDE, convertToDMS(latitude));
mappedProperties.put(ExifInterface.TAG_GPS_LATITUDE_REF, latitude >= 0 ? "N" : "S");
mappedProperties.put(ExifInterface.TAG_GPS_LONGITUDE, convertToDMS(longitude));
mappedProperties.put(ExifInterface.TAG_GPS_LONGITUDE_REF, longitude >= 0 ? "N" : "S");
mappedProperties.put(ExifInterface.TAG_GPS_ALTITUDE, convertToRational(altitude));
mappedProperties.put(ExifInterface.TAG_GPS_ALTITUDE_REF, altitude >= 0 ? "0" : "1");
}

if (properties.hasKey("UserComment")) {
mappedProperties.put(ExifInterface.TAG_USER_COMMENT, properties.getString("UserComment"));
}

saveImageAndModifyProperties(base64ImageData, mappedProperties, promise);
} catch (Exception e) {
promise.reject("E_IMAGE_PROCESSING", e);
}
}

private void saveImageAndModifyProperties(String base64ImageData, Map<String, String> properties, Promise promise) throws IOException {
Context context = getReactApplicationContext();

@ReactMethod
public void saveImageWithUserComment(String base64ImageData, String userComment, Promise promise) {
try {
Map<String, String> properties = new HashMap<>();
// Decode the base64 string to a bitmap
byte[] decodedString = Base64.decode(base64ImageData, Base64.DEFAULT);
Bitmap image = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);

properties.put(ExifInterface.TAG_USER_COMMENT, userComment);
saveImageWithProperties(base64ImageData, properties, promise);
} catch (Exception e) {
promise.reject("E_IMAGE_PROCESSING", e);
}
// Prepare ContentValues to create a new media file
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");

// Insert the new file to the media store
Uri uri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
if (uri == null) {
promise.reject("E_FILE_CREATION", "Failed to create new MediaStore entry.");
return;
}

@ReactMethod
public void saveImageWithProperties(String base64ImageData, Map<String, String> properties, Promise promise) throws IOException {
Context context = getReactApplicationContext();

// Decode the base64 string to a bitmap
byte[] decodedString = Base64.decode(base64ImageData, Base64.DEFAULT);
Bitmap image = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);

// Prepare ContentValues to create a new media file
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");

// Insert the new file to the media store
Uri uri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
if (uri == null) {
promise.reject("E_FILE_CREATION", "Failed to create new MediaStore entry.");
return;
}

try (FileOutputStream fos = (FileOutputStream) context.getContentResolver().openOutputStream(uri)) {
image.compress(Bitmap.CompressFormat.JPEG, 100, fos);
}

// Modify the EXIF data
String filePath = getRealPathFromURI(context, uri);
ExifInterface exifInterface = new ExifInterface(filePath);
for (Map.Entry<String, String> entry : properties.entrySet()) {
exifInterface.setAttribute(entry.getKey(), entry.getValue());
}
exifInterface.saveAttributes();

promise.resolve(uri.toString());
try (FileOutputStream fos = (FileOutputStream) context.getContentResolver().openOutputStream(uri)) {
image.compress(Bitmap.CompressFormat.JPEG, 100, fos);
}

private String getRealPathFromURI(Context context, Uri contentUri) {
Cursor cursor = null;
// Modify the EXIF data
String filePath = getRealPathFromURI(context, uri);
ExifInterface exifInterface = new ExifInterface(filePath);
for (Map.Entry<String, String> entry : properties.entrySet()) {
exifInterface.setAttribute(entry.getKey(), entry.getValue());
}
exifInterface.saveAttributes();

try {
String[] proj = { MediaStore.Images.Media.DATA };
cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
promise.resolve(uri.toString());
}

cursor.moveToFirst();
private String getRealPathFromURI(Context context, Uri contentUri) {
Cursor cursor = null;

return cursor.getString(column_index);
} finally {
if (cursor != null) {
cursor.close();
}
}
try {
String[] proj = {MediaStore.Images.Media.DATA};
cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);

cursor.moveToFirst();

return cursor.getString(column_index);
} finally {
if (cursor != null) {
cursor.close();
}
}
}

private String convertToRational(double value) {
return (int) value + "/1";
}

private String convertToDMS(double coordinate) {
coordinate = Math.abs(coordinate);
int degrees = (int) coordinate;
coordinate = (coordinate - degrees) * 60;
int minutes = (int) coordinate;
coordinate = (coordinate - minutes) * 60;
int seconds = (int) (coordinate * 1000);

return degrees + "/1," + minutes + "/1," + seconds + "/1000";
}
}

22 changes: 14 additions & 8 deletions ios/ExifModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,21 @@ class ExifModifier: NSObject {
kCGImagePropertyExifUserComment as String: properties["UserComment"]
].compactMapValues { $0 }

let latitude = properties["GPSLatitude"] as? Double
let longitude = properties["GPSLongitude"] as? Double
let altitude = properties["GPSAltitude"] as? Double

let latitudeRef = (latitude ?? 0.0) >= 0 ? "N" : "S"
let longitudeRef = (longitude ?? 0.0) >= 0 ? "E" : "W"
let altitudeRef = (altitude ?? 0.0) >= 0 ? 0 : 1

let gpsProperties: [String: Any] = [
kCGImagePropertyGPSLatitude as String: properties["GPSLatitude"],
kCGImagePropertyGPSLatitudeRef as String: properties["GPSLatitudeRef"],
kCGImagePropertyGPSLongitude as String: properties["GPSLongitude"],
kCGImagePropertyGPSLongitudeRef as String: properties["GPSLongitudeRef"],
kCGImagePropertyGPSAltitude as String: properties["GPSAltitude"],
kCGImagePropertyGPSAltitudeRef as String: properties["GPSAltitudeRef"],
kCGImagePropertyGPSTimeStamp as String: properties["GPSTimeStamp"],
kCGImagePropertyGPSDateStamp as String: properties["GPSDateStamp"]
kCGImagePropertyGPSLatitude as String: latitude,
kCGImagePropertyGPSLatitudeRef as String: latitudeRef,
kCGImagePropertyGPSLongitude as String: longitude,
kCGImagePropertyGPSLongitudeRef as String: longitudeRef,
kCGImagePropertyGPSAltitude as String: altitude,
kCGImagePropertyGPSAltitudeRef as String: altitudeRef,
].compactMapValues { $0 }

let mappedProperties: NSDictionary = [
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lulububu/react-native-exif-modifier",
"version": "0.1.10",
"version": "0.1.11",
"description": "Allows you to modify the exif data of an image and save it.",
"main": "lib/commonjs/index",
"module": "lib/module/index",
Expand Down
5 changes: 0 additions & 5 deletions src/interfaces/ImageProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@
export interface ImageProperties {
UserComment?: string;
GPSLatitude?: string;
GPSLatitudeRef?: string;
GPSLongitude?: string;
GPSLongitudeRef?: string;
GPSAltitude?: string;
GPSAltitudeRef?: string;
GPSTimeStamp?: string;
GPSDateStamp?: string;
}

0 comments on commit fd1e204

Please sign in to comment.