From fd1e204d7cec177eb8af85e528e90fe79abeb046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Hau=C3=9Fmann?= Date: Fri, 23 Aug 2024 18:40:37 +0200 Subject: [PATCH] added location support for android and raised package version --- .../com/exifmodifier/ExifModifierModule.java | 173 ++++++++++++------ ios/ExifModifier.swift | 22 ++- package.json | 2 +- src/interfaces/ImageProperties.ts | 5 - 4 files changed, 128 insertions(+), 74 deletions(-) diff --git a/android/src/main/java/com/exifmodifier/ExifModifierModule.java b/android/src/main/java/com/exifmodifier/ExifModifierModule.java index 836e7d9..6ec8910 100644 --- a/android/src/main/java/com/exifmodifier/ExifModifierModule.java +++ b/android/src/main/java/com/exifmodifier/ExifModifierModule.java @@ -14,6 +14,8 @@ 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; @@ -21,78 +23,129 @@ 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 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 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 properties, Promise promise) throws IOException { + Context context = getReactApplicationContext(); - @ReactMethod - public void saveImageWithUserComment(String base64ImageData, String userComment, Promise promise) { - try { - Map 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 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 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 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"; + } } + diff --git a/ios/ExifModifier.swift b/ios/ExifModifier.swift index e6710d7..3dac53f 100644 --- a/ios/ExifModifier.swift +++ b/ios/ExifModifier.swift @@ -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 = [ diff --git a/package.json b/package.json index f50f365..a475198 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/interfaces/ImageProperties.ts b/src/interfaces/ImageProperties.ts index 05f524a..9769c0a 100644 --- a/src/interfaces/ImageProperties.ts +++ b/src/interfaces/ImageProperties.ts @@ -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; }