diff --git a/android/src/main/java/com/lodev09/exify/ExifyModule.kt b/android/src/main/java/com/lodev09/exify/ExifyModule.kt
index f6fbe8d..6d8fb86 100644
--- a/android/src/main/java/com/lodev09/exify/ExifyModule.kt
+++ b/android/src/main/java/com/lodev09/exify/ExifyModule.kt
@@ -42,7 +42,7 @@ class ExifyModule(
// On Android Q+, request ACCESS_MEDIA_LOCATION for unredacted GPS data
if (scheme == "content" && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_MEDIA_LOCATION) !=
- PackageManager.PERMISSION_GRANTED
+ PackageManager.PERMISSION_GRANTED
) {
val activity = context.currentActivity as? PermissionAwareActivity
if (activity != null) {
@@ -156,14 +156,46 @@ class ExifyModule(
tags.hasKey(ExifInterface.TAG_GPS_LATITUDE) &&
tags.hasKey(ExifInterface.TAG_GPS_LONGITUDE)
) {
- exif.setLatLong(
- tags.getDouble(ExifInterface.TAG_GPS_LATITUDE),
- tags.getDouble(ExifInterface.TAG_GPS_LONGITUDE),
- )
+ val hasExplicitRef =
+ tags.hasKey(ExifInterface.TAG_GPS_LATITUDE_REF) ||
+ tags.hasKey(ExifInterface.TAG_GPS_LONGITUDE_REF)
+
+ if (hasExplicitRef) {
+ val lat = tags.getDouble(ExifInterface.TAG_GPS_LATITUDE)
+ val lng = tags.getDouble(ExifInterface.TAG_GPS_LONGITUDE)
+ val latRef =
+ if (tags.hasKey(ExifInterface.TAG_GPS_LATITUDE_REF)) {
+ tags.getString(ExifInterface.TAG_GPS_LATITUDE_REF)
+ } else {
+ if (lat >= 0) "N" else "S"
+ }
+ val lngRef =
+ if (tags.hasKey(ExifInterface.TAG_GPS_LONGITUDE_REF)) {
+ tags.getString(ExifInterface.TAG_GPS_LONGITUDE_REF)
+ } else {
+ if (lng >= 0) "E" else "W"
+ }
+
+ exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, ExifyUtils.decimalToDms(Math.abs(lat)))
+ exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, latRef)
+ exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, ExifyUtils.decimalToDms(Math.abs(lng)))
+ exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, lngRef)
+ } else {
+ exif.setLatLong(
+ tags.getDouble(ExifInterface.TAG_GPS_LATITUDE),
+ tags.getDouble(ExifInterface.TAG_GPS_LONGITUDE),
+ )
+ }
}
if (tags.hasKey(ExifInterface.TAG_GPS_ALTITUDE)) {
- exif.setAltitude(tags.getDouble(ExifInterface.TAG_GPS_ALTITUDE))
+ val alt = tags.getDouble(ExifInterface.TAG_GPS_ALTITUDE)
+ if (tags.hasKey(ExifInterface.TAG_GPS_ALTITUDE_REF)) {
+ exif.setAttribute(ExifInterface.TAG_GPS_ALTITUDE, ExifyUtils.decimalToRational(Math.abs(alt)))
+ exif.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF, tags.getInt(ExifInterface.TAG_GPS_ALTITUDE_REF).toString())
+ } else {
+ exif.setAltitude(alt)
+ }
}
params.putString("uri", uri)
diff --git a/android/src/main/java/com/lodev09/exify/ExifyUtils.kt b/android/src/main/java/com/lodev09/exify/ExifyUtils.kt
index 3e331ee..6421bd1 100644
--- a/android/src/main/java/com/lodev09/exify/ExifyUtils.kt
+++ b/android/src/main/java/com/lodev09/exify/ExifyUtils.kt
@@ -46,4 +46,19 @@ object ExifyUtils {
return tags
}
+
+ @JvmStatic
+ fun decimalToDms(decimal: Double): String {
+ val degrees = decimal.toInt()
+ val minutesDecimal = (decimal - degrees) * 60
+ val minutes = minutesDecimal.toInt()
+ val seconds = ((minutesDecimal - minutes) * 60 * 10000).toLong()
+ return "$degrees/1,$minutes/1,$seconds/10000"
+ }
+
+ @JvmStatic
+ fun decimalToRational(value: Double): String {
+ val numerator = (value * 10000).toLong()
+ return "$numerator/10000"
+ }
}
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 46d1d6c..f8a54d6 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -40,8 +40,10 @@ export default function App() {
const writeExif = async (uri: string) => {
const [lng, lat] = mockPosition();
const tags: ExifTags = {
- GPSLatitude: lat,
- GPSLongitude: lng,
+ GPSLatitude: Math.abs(lat),
+ GPSLatitudeRef: lat >= 0 ? 'N' : 'S',
+ GPSLongitude: Math.abs(lng),
+ GPSLongitudeRef: lng >= 0 ? 'E' : 'W',
GPSTimeStamp: '10:10:10',
GPSDateStamp: '2024:10:10',
GPSDOP: 5.0,
@@ -61,6 +63,18 @@ export default function App() {
return result;
};
+ const readWriteRoundTrip = async (uri: string) => {
+ const tags = await Exify.read(uri);
+ if (!tags) return;
+
+ console.log('roundTrip read:', json(tags));
+ const result = await Exify.write(uri, tags);
+ console.log('roundTrip write:', json(result));
+
+ const verify = await Exify.read(uri);
+ console.log('roundTrip verify:', json(verify));
+ };
+
const takePhoto = async () => {
if (!cameraRef.current) return;
@@ -79,27 +93,42 @@ export default function App() {
await readExif(asset.uri);
};
- const openLibrary = async () => {
+ const pickImage = async () => {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ['images'],
quality: 1,
});
- if (result.canceled) return;
+ if (result.canceled) return null;
const asset = result.assets[0];
- if (!asset) return;
+ if (!asset) return null;
const uri =
Platform.OS === 'ios' && asset.assetId
? `ph://${asset.assetId}`
: asset.uri;
- console.log('openLibrary:', uri);
setPreview(asset.uri);
+ return uri;
+ };
+
+ const openLibrary = async () => {
+ const uri = await pickImage();
+ if (!uri) return;
+
+ console.log('openLibrary:', uri);
await readExif(uri);
};
+ const testRoundTrip = async () => {
+ const uri = await pickImage();
+ if (!uri) return;
+
+ console.log('testRoundTrip:', uri);
+ await readWriteRoundTrip(uri);
+ };
+
const openUrl = async () => {
const defaultUrl =
'https://raw.githubusercontent.com/ianare/exif-samples/master/jpg/gps/DSCN0010.jpg';
@@ -139,7 +168,11 @@ export default function App() {
-
+
{preview && (
)}
@@ -147,7 +180,7 @@ export default function App() {
-
+
URL
@@ -180,11 +213,15 @@ const styles = StyleSheet.create({
backgroundColor: '#000',
width: '100%',
},
- previewButton: {
+ sideButton: {
width: 50,
height: 50,
borderRadius: 8,
backgroundColor: '#333',
+ borderWidth: 1,
+ borderColor: '#555',
+ alignItems: 'center',
+ justifyContent: 'center',
overflow: 'hidden',
},
preview: {
@@ -206,14 +243,6 @@ const styles = StyleSheet.create({
borderRadius: 29,
backgroundColor: '#fff',
},
- urlButton: {
- width: 50,
- height: 50,
- borderRadius: 8,
- backgroundColor: '#333',
- alignItems: 'center',
- justifyContent: 'center',
- },
urlLabel: {
color: '#fff',
fontSize: 13,
diff --git a/ios/Exify.mm b/ios/Exify.mm
index ee850ad..dbfc4e7 100644
--- a/ios/Exify.mm
+++ b/ios/Exify.mm
@@ -3,14 +3,13 @@
static NSSet *tiffKeys;
__attribute__((constructor)) static void initTiffKeys(void) {
- tiffKeys = [NSSet setWithObjects:@"Make", @"Model", @"Software", @"DateTime",
- @"Artist", @"Copyright", @"ImageDescription",
- @"Orientation", @"XResolution",
- @"YResolution", @"ResolutionUnit",
- @"Compression", @"PhotometricInterpretation",
- @"TransferFunction", @"WhitePoint",
- @"PrimaryChromaticities", @"HostComputer",
- nil];
+ tiffKeys = [NSSet
+ setWithObjects:@"Make", @"Model", @"Software", @"DateTime", @"Artist",
+ @"Copyright", @"ImageDescription", @"Orientation",
+ @"XResolution", @"YResolution", @"ResolutionUnit",
+ @"Compression", @"PhotometricInterpretation",
+ @"TransferFunction", @"WhitePoint",
+ @"PrimaryChromaticities", @"HostComputer", nil];
}
#pragma mark - Helpers
@@ -122,6 +121,11 @@ static void addTagEntries(CFStringRef dictionary, NSDictionary *metadata,
kCGImagePropertyGPSDictionary]
?: @{}];
+ // Pre-read explicit GPS ref values so coordinate handlers can use them
+ NSString *latRef = tags[@"GPSLatitudeRef"];
+ NSString *lngRef = tags[@"GPSLongitudeRef"];
+ NSNumber *altRef = tags[@"GPSAltitudeRef"];
+
for (NSString *key in tags) {
id value = tags[key];
@@ -129,18 +133,21 @@ static void addTagEntries(CFStringRef dictionary, NSDictionary *metadata,
double lat = [value doubleValue];
gpsDict[(__bridge NSString *)kCGImagePropertyGPSLatitude] = @(fabs(lat));
gpsDict[(__bridge NSString *)kCGImagePropertyGPSLatitudeRef] =
- lat >= 0 ? @"N" : @"S";
+ latRef ?: (lat >= 0 ? @"N" : @"S");
} else if ([key isEqualToString:@"GPSLongitude"]) {
double lng = [value doubleValue];
- gpsDict[(__bridge NSString *)kCGImagePropertyGPSLongitude] =
- @(fabs(lng));
+ gpsDict[(__bridge NSString *)kCGImagePropertyGPSLongitude] = @(fabs(lng));
gpsDict[(__bridge NSString *)kCGImagePropertyGPSLongitudeRef] =
- lng >= 0 ? @"E" : @"W";
+ lngRef ?: (lng >= 0 ? @"E" : @"W");
} else if ([key isEqualToString:@"GPSAltitude"]) {
double alt = [value doubleValue];
gpsDict[(__bridge NSString *)kCGImagePropertyGPSAltitude] = @(fabs(alt));
gpsDict[(__bridge NSString *)kCGImagePropertyGPSAltitudeRef] =
- @(alt >= 0 ? 0 : 1);
+ altRef ?: @(alt >= 0 ? 0 : 1);
+ } else if ([key isEqualToString:@"GPSLatitudeRef"] ||
+ [key isEqualToString:@"GPSLongitudeRef"] ||
+ [key isEqualToString:@"GPSAltitudeRef"]) {
+ // Already handled above
} else if ([key hasPrefix:@"GPS"]) {
gpsDict[[key substringFromIndex:3]] = value;
} else if ([tiffKeys containsObject:key]) {