Skip to content

Commit

Permalink
fix(ios): preserving EXIF data (#712)
Browse files Browse the repository at this point in the history
  • Loading branch information
codeconsole committed Mar 22, 2022
1 parent 53223c3 commit 4608f8e
Showing 1 changed file with 177 additions and 21 deletions.
198 changes: 177 additions & 21 deletions src/ios/CDVCamera.m
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Licensed to the Apache Software Foundation (ASF) under one
#import <ImageIO/CGImageDestination.h>
#import <MobileCoreServices/UTCoreTypes.h>
#import <objc/message.h>
#import <Photos/Photos.h>

#ifndef __CORDOVA_4_0_0
#import <Cordova/NSData+Base64.h>
Expand Down Expand Up @@ -159,7 +160,7 @@ - (void)takePicture:(CDVInvokedUrlCommand*)command
if (pictureOptions.sourceType == UIImagePickerControllerSourceTypeCamera) {
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted)
{
if(!granted)
if (!granted)
{
// Denied; show an alert
dispatch_async(dispatch_get_main_queue(), ^{
Expand All @@ -174,11 +175,32 @@ - (void)takePicture:(CDVInvokedUrlCommand*)command
[weakSelf.viewController presentViewController:alertController animated:YES completion:nil];
});
} else {
[weakSelf showCameraPicker:command.callbackId withOptions:pictureOptions];
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf showCameraPicker:command.callbackId withOptions:pictureOptions];
});
}
}];
} else {
[weakSelf showCameraPicker:command.callbackId withOptions:pictureOptions];
[weakSelf options:pictureOptions requestPhotoPermissions:^(BOOL granted) {
if (!granted) {
// Denied; show an alert
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"] message:NSLocalizedString(@"Access to the camera roll has been prohibited; please enable it in the Settings to continue.", nil) preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[weakSelf sendNoPermissionResult:command.callbackId];
}]];
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Settings", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
[weakSelf sendNoPermissionResult:command.callbackId];
}]];
[weakSelf.viewController presentViewController:alertController animated:YES completion:nil];
});
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf showCameraPicker:command.callbackId withOptions:pictureOptions];
});
}
}];
}
}];
}
Expand Down Expand Up @@ -367,24 +389,51 @@ - (NSData*)processImage:(UIImage*)image info:(NSDictionary*)info options:(CDVPic
data = UIImageJPEGRepresentation(image, [options.quality floatValue] / 100.0f);
}

if (options.usesGeolocation) {
NSDictionary* controllerMetadata = [info objectForKey:@"UIImagePickerControllerMediaMetadata"];
if (pickerController.sourceType == UIImagePickerControllerSourceTypeCamera) {
if (options.usesGeolocation) {
NSDictionary* controllerMetadata = [info objectForKey:@"UIImagePickerControllerMediaMetadata"];
if (controllerMetadata) {
self.data = data;
self.metadata = [[NSMutableDictionary alloc] init];

NSMutableDictionary* EXIFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyExifDictionary]mutableCopy];
if (EXIFDictionary) {
[self.metadata setObject:EXIFDictionary forKey:(NSString*)kCGImagePropertyExifDictionary];
}

if (IsAtLeastiOSVersion(@"8.0")) {
[[self locationManager] performSelector:NSSelectorFromString(@"requestWhenInUseAuthorization") withObject:nil afterDelay:0];
}
[[self locationManager] startUpdatingLocation];
}
data = nil;
}
} else if (pickerController.sourceType == UIImagePickerControllerSourceTypePhotoLibrary) {
PHAsset* asset = [info objectForKey:@"UIImagePickerControllerPHAsset"];
NSDictionary* controllerMetadata = [self getImageMetadataFromAsset:asset];

self.data = data;
if (controllerMetadata) {
self.data = data;
self.metadata = [[NSMutableDictionary alloc] init];

NSMutableDictionary* EXIFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyExifDictionary]mutableCopy];
if (EXIFDictionary) {
[self.metadata setObject:EXIFDictionary forKey:(NSString*)kCGImagePropertyExifDictionary];
}

if (IsAtLeastiOSVersion(@"8.0")) {
[[self locationManager] performSelector:NSSelectorFromString(@"requestWhenInUseAuthorization") withObject:nil afterDelay:0];
NSMutableDictionary* TIFFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyTIFFDictionary
]mutableCopy];
if (TIFFDictionary) {
[self.metadata setObject:TIFFDictionary forKey:(NSString*)kCGImagePropertyTIFFDictionary];
}
NSMutableDictionary* GPSDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyGPSDictionary
]mutableCopy];
if (GPSDictionary) {
[self.metadata setObject:GPSDictionary forKey:(NSString*)kCGImagePropertyGPSDictionary
];
}
[[self locationManager] startUpdatingLocation];
}
data = nil;
}

}
break;
default:
Expand All @@ -394,6 +443,78 @@ - (NSData*)processImage:(UIImage*)image info:(NSDictionary*)info options:(CDVPic
return data;
}

/* --------------------------------------------------------------
-- get the metadata of the image from a PHAsset
-------------------------------------------------------------- */
- (NSDictionary*)getImageMetadataFromAsset:(PHAsset*)asset {

if(asset == nil) {
return nil;
}

// get photo info from this asset
__block NSDictionary *dict = nil;
PHImageRequestOptions *imageRequestOptions = [[PHImageRequestOptions alloc] init];
imageRequestOptions.synchronous = YES;
[[PHImageManager defaultManager]
requestImageDataForAsset:asset
options:imageRequestOptions
resultHandler: ^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
dict = [self convertImageMetadata:imageData]; // as this imageData is in NSData format so we need a method to convert this NSData into NSDictionary
}];
return dict;
}

-(NSDictionary*)convertImageMetadata:(NSData*)imageData {
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)(imageData), NULL);
if (imageSource) {
NSDictionary *options = @{(NSString *)kCGImageSourceShouldCache : [NSNumber numberWithBool:NO]};
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);
if (imageProperties) {
NSDictionary *metadata = (__bridge NSDictionary *)imageProperties;
CFRelease(imageProperties);
CFRelease(imageSource);
NSLog(@"Metadata of selected image%@", metadata);// image metadata after converting NSData into NSDictionary
return metadata;
}
CFRelease(imageSource);
}

NSLog(@"Can't read image metadata");
return nil;
}

- (void)options:(CDVPictureOptions*)options requestPhotoPermissions:(void (^)(BOOL auth))completion
{
if((unsigned long)options.sourceType == 1){
completion(YES);
}
else{
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];

switch (status) {
case PHAuthorizationStatusAuthorized:
completion(YES);
break;
case PHAuthorizationStatusNotDetermined: {
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus authorizationStatus) {
if (authorizationStatus == PHAuthorizationStatusAuthorized) {
completion(YES);
} else {
completion(NO);
}
}];
break;
}
default:
completion(NO);
break;
}

}

}

- (NSString*)tempFilePath:(NSString*)extension
{
NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath];
Expand Down Expand Up @@ -454,17 +575,48 @@ - (void)resultForImage:(CDVPictureOptions*)options info:(NSDictionary*)info comp
image = [self retrieveImage:info options:options];
NSData* data = [self processImage:image info:info options:options];
if (data) {
if (pickerController.sourceType == UIImagePickerControllerSourceTypePhotoLibrary) {
NSMutableData *imageDataWithExif = [NSMutableData data];
if (self.metadata) {
CGImageSourceRef sourceImage = CGImageSourceCreateWithData((__bridge CFDataRef)self.data, NULL);
CFStringRef sourceType = CGImageSourceGetType(sourceImage);

CGImageDestinationRef destinationImage = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageDataWithExif, sourceType, 1, NULL);
CGImageDestinationAddImageFromSource(destinationImage, sourceImage, 0, (__bridge CFDictionaryRef)self.metadata);
CGImageDestinationFinalize(destinationImage);

CFRelease(sourceImage);
CFRelease(destinationImage);
} else {
imageDataWithExif = [self.data mutableCopy];
}

NSString* extension = options.encodingType == EncodingTypePNG? @"png" : @"jpg";
NSString* filePath = [self tempFilePath:extension];
NSError* err = nil;
NSError* err = nil;
NSString* extension = self.pickerController.pictureOptions.encodingType == EncodingTypePNG ? @"png":@"jpg";
NSString* filePath = [self tempFilePath:extension];

// save file
if (![data writeToFile:filePath options:NSAtomicWrite error:&err]) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
} else {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[self urlTransformer:[NSURL fileURLWithPath:filePath]] absoluteString]];
// save file
if (![imageDataWithExif writeToFile:filePath options:NSAtomicWrite error:&err]) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
}
else {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[self urlTransformer:[NSURL fileURLWithPath:filePath]] absoluteString]];
}

} else if (pickerController.sourceType != UIImagePickerControllerSourceTypeCamera || !options.usesGeolocation) {
// No need to save file if usesGeolocation is true since it will be saved after the location is tracked
NSString* extension = options.encodingType == EncodingTypePNG? @"png" : @"jpg";
NSString* filePath = [self tempFilePath:extension];
NSError* err = nil;

// save file
if (![data writeToFile:filePath options:NSAtomicWrite error:&err]) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
} else {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[self urlTransformer:[NSURL fileURLWithPath:filePath]] absoluteString]];
}
}

}
}
break;
Expand Down Expand Up @@ -649,19 +801,23 @@ - (void)imagePickerControllerReturnImageResult
{
CDVPictureOptions* options = self.pickerController.pictureOptions;
CDVPluginResult* result = nil;

NSMutableData *imageDataWithExif = [NSMutableData data];

if (self.metadata) {
NSData* dataCopy = [self.data mutableCopy];
CGImageSourceRef sourceImage = CGImageSourceCreateWithData((__bridge CFDataRef)dataCopy, NULL);
CFStringRef sourceType = CGImageSourceGetType(sourceImage);

CGImageDestinationRef destinationImage = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)self.data, sourceType, 1, NULL);
CGImageDestinationRef destinationImage = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageDataWithExif, sourceType, 1, NULL);
CGImageDestinationAddImageFromSource(destinationImage, sourceImage, 0, (__bridge CFDictionaryRef)self.metadata);
CGImageDestinationFinalize(destinationImage);

dataCopy = nil;
CFRelease(sourceImage);
CFRelease(destinationImage);
} else {
imageDataWithExif = [self.data mutableCopy];
}

switch (options.destinationType) {
Expand Down Expand Up @@ -695,7 +851,7 @@ - (void)imagePickerControllerReturnImageResult
self.pickerController = nil;
self.data = nil;
self.metadata = nil;

imageDataWithExif = nil;
if (options.saveToPhotoAlbum) {
UIImageWriteToSavedPhotosAlbum([[UIImage alloc] initWithData:self.data], nil, nil, nil);
}
Expand Down

0 comments on commit 4608f8e

Please sign in to comment.