fix(ios): preserving EXIF data (#712)

This commit is contained in:
Scott Murphy 2022-03-22 00:23:56 -07:00 committed by GitHub
parent 53223c3df2
commit 4608f8ef80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -29,6 +29,7 @@
#import <ImageIO/CGImageDestination.h> #import <ImageIO/CGImageDestination.h>
#import <MobileCoreServices/UTCoreTypes.h> #import <MobileCoreServices/UTCoreTypes.h>
#import <objc/message.h> #import <objc/message.h>
#import <Photos/Photos.h>
#ifndef __CORDOVA_4_0_0 #ifndef __CORDOVA_4_0_0
#import <Cordova/NSData+Base64.h> #import <Cordova/NSData+Base64.h>
@ -159,7 +160,7 @@ static NSString* toBase64(NSData* data) {
if (pictureOptions.sourceType == UIImagePickerControllerSourceTypeCamera) { if (pictureOptions.sourceType == UIImagePickerControllerSourceTypeCamera) {
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted)
{ {
if(!granted) if (!granted)
{ {
// Denied; show an alert // Denied; show an alert
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
@ -174,11 +175,32 @@ static NSString* toBase64(NSData* data) {
[weakSelf.viewController presentViewController:alertController animated:YES completion:nil]; [weakSelf.viewController presentViewController:alertController animated:YES completion:nil];
}); });
} else { } else {
[weakSelf showCameraPicker:command.callbackId withOptions:pictureOptions]; dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf showCameraPicker:command.callbackId withOptions:pictureOptions];
});
} }
}]; }];
} else { } 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];
});
}
}];
} }
}]; }];
} }
@ -367,24 +389,51 @@ static NSString* toBase64(NSData* data) {
data = UIImageJPEGRepresentation(image, [options.quality floatValue] / 100.0f); data = UIImageJPEGRepresentation(image, [options.quality floatValue] / 100.0f);
} }
if (options.usesGeolocation) { if (pickerController.sourceType == UIImagePickerControllerSourceTypeCamera) {
NSDictionary* controllerMetadata = [info objectForKey:@"UIImagePickerControllerMediaMetadata"]; 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) { if (controllerMetadata) {
self.data = data;
self.metadata = [[NSMutableDictionary alloc] init]; self.metadata = [[NSMutableDictionary alloc] init];
NSMutableDictionary* EXIFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyExifDictionary]mutableCopy]; NSMutableDictionary* EXIFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyExifDictionary]mutableCopy];
if (EXIFDictionary) { if (EXIFDictionary) {
[self.metadata setObject:EXIFDictionary forKey:(NSString*)kCGImagePropertyExifDictionary]; [self.metadata setObject:EXIFDictionary forKey:(NSString*)kCGImagePropertyExifDictionary];
} }
NSMutableDictionary* TIFFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyTIFFDictionary
if (IsAtLeastiOSVersion(@"8.0")) { ]mutableCopy];
[[self locationManager] performSelector:NSSelectorFromString(@"requestWhenInUseAuthorization") withObject:nil afterDelay:0]; 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; break;
default: default:
@ -394,6 +443,78 @@ static NSString* toBase64(NSData* data) {
return data; 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*)tempFilePath:(NSString*)extension
{ {
NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath]; NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath];
@ -454,17 +575,48 @@ static NSString* toBase64(NSData* data) {
image = [self retrieveImage:info options:options]; image = [self retrieveImage:info options:options];
NSData* data = [self processImage:image info:info options:options]; NSData* data = [self processImage:image info:info options:options];
if (data) { 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);
NSString* extension = options.encodingType == EncodingTypePNG? @"png" : @"jpg"; CGImageDestinationRef destinationImage = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageDataWithExif, sourceType, 1, NULL);
NSString* filePath = [self tempFilePath:extension]; CGImageDestinationAddImageFromSource(destinationImage, sourceImage, 0, (__bridge CFDictionaryRef)self.metadata);
NSError* err = nil; CGImageDestinationFinalize(destinationImage);
// save file CFRelease(sourceImage);
if (![data writeToFile:filePath options:NSAtomicWrite error:&err]) { CFRelease(destinationImage);
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]]; } else {
} else { imageDataWithExif = [self.data mutableCopy];
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[self urlTransformer:[NSURL fileURLWithPath:filePath]] absoluteString]]; }
NSError* err = nil;
NSString* extension = self.pickerController.pictureOptions.encodingType == EncodingTypePNG ? @"png":@"jpg";
NSString* filePath = [self tempFilePath:extension];
// 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; break;
@ -649,19 +801,23 @@ static NSString* toBase64(NSData* data) {
{ {
CDVPictureOptions* options = self.pickerController.pictureOptions; CDVPictureOptions* options = self.pickerController.pictureOptions;
CDVPluginResult* result = nil; CDVPluginResult* result = nil;
NSMutableData *imageDataWithExif = [NSMutableData data];
if (self.metadata) { if (self.metadata) {
NSData* dataCopy = [self.data mutableCopy]; NSData* dataCopy = [self.data mutableCopy];
CGImageSourceRef sourceImage = CGImageSourceCreateWithData((__bridge CFDataRef)dataCopy, NULL); CGImageSourceRef sourceImage = CGImageSourceCreateWithData((__bridge CFDataRef)dataCopy, NULL);
CFStringRef sourceType = CGImageSourceGetType(sourceImage); 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); CGImageDestinationAddImageFromSource(destinationImage, sourceImage, 0, (__bridge CFDictionaryRef)self.metadata);
CGImageDestinationFinalize(destinationImage); CGImageDestinationFinalize(destinationImage);
dataCopy = nil; dataCopy = nil;
CFRelease(sourceImage); CFRelease(sourceImage);
CFRelease(destinationImage); CFRelease(destinationImage);
} else {
imageDataWithExif = [self.data mutableCopy];
} }
switch (options.destinationType) { switch (options.destinationType) {
@ -695,7 +851,7 @@ static NSString* toBase64(NSData* data) {
self.pickerController = nil; self.pickerController = nil;
self.data = nil; self.data = nil;
self.metadata = nil; self.metadata = nil;
imageDataWithExif = nil;
if (options.saveToPhotoAlbum) { if (options.saveToPhotoAlbum) {
UIImageWriteToSavedPhotosAlbum([[UIImage alloc] initWithData:self.data], nil, nil, nil); UIImageWriteToSavedPhotosAlbum([[UIImage alloc] initWithData:self.data], nil, nil, nil);
} }