Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ios): Keep the metadata of image, add GPS when take picture from Camera (CB-6708) #230

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 83 additions & 50 deletions src/ios/CDVCamera.m
Original file line number Diff line number Diff line change
Expand Up @@ -240,13 +240,9 @@ - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)butto

- (void)repositionPopover:(CDVInvokedUrlCommand*)command
{
if (([[self pickerController] pickerPopoverController] != nil) && [[[self pickerController] pickerPopoverController] isPopoverVisible]) {
NSDictionary* options = [command argumentAtIndex:0 withDefault:nil];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi.
Thanks for the PR.

I think you forked you repo before this changes were made and didn't sync before sending your PR, but this code is important to fix another bug.
Can you make sure that your fork is in sync with the latest code on this repo and then send the PR with just the relevant code to fix the issue?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi.
I have sync code to my forks.


[[[self pickerController] pickerPopoverController] dismissPopoverAnimated:NO];

NSDictionary* options = [command argumentAtIndex:0 withDefault:nil];
[self displayPopover:options];
}
[self displayPopover:options];
}

- (NSInteger)integerValueForKey:(NSDictionary*)dict key:(NSString*)key defaultValue:(NSInteger)defaultValue
Expand Down Expand Up @@ -348,47 +344,77 @@ - (void)popoverControllerDidDismissPopover:(id)popoverController
self.hasPendingOperation = NO;
}

- (NSData*)processImage:(UIImage*)image info:(NSDictionary*)info options:(CDVPictureOptions*)options
// Modified by Leckie. 2016-08-12
- (void)processImage:(UIImage*)image info:(NSDictionary*)info options:(CDVPictureOptions*)options completion:(void (^)())completion
{
NSData* data = nil;

switch (options.encodingType) {
case EncodingTypePNG:
data = UIImagePNGRepresentation(image);
self.data = UIImagePNGRepresentation(image);
completion();
break;
case EncodingTypeJPEG:
{
if ((options.allowsEditing == NO) && (options.targetSize.width <= 0) && (options.targetSize.height <= 0) && (options.correctOrientation == NO) && (([options.quality integerValue] == 100) || (options.sourceType != UIImagePickerControllerSourceTypeCamera))){
// use image unedited as requested , don't resize
data = UIImageJPEGRepresentation(image, 1.0);
self.data = UIImageJPEGRepresentation(image, 1.0);
} else {
data = UIImageJPEGRepresentation(image, [options.quality floatValue] / 100.0f);
self.data = UIImageJPEGRepresentation(image, [options.quality floatValue] / 100.0f);
}

if (options.usesGeolocation) {
NSDictionary* controllerMetadata = [info objectForKey:@"UIImagePickerControllerMediaMetadata"];
if (controllerMetadata) {
self.data = data;
// add metedata as much as possible
NSDictionary* controllerMetadata = [info objectForKey:UIImagePickerControllerMediaMetadata];
if (controllerMetadata) {
/*NSMutableDictionary* EXIFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyExifDictionary]mutableCopy];
if (EXIFDictionary) {
self.metadata = [[NSMutableDictionary alloc] init];

NSMutableDictionary* EXIFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyExifDictionary]mutableCopy];
if (EXIFDictionary) {
[self.metadata setObject:EXIFDictionary forKey:(NSString*)kCGImagePropertyExifDictionary];
}*/
self.metadata = [controllerMetadata mutableCopy];
completion();
} else {
// just try
NSURL *assertURL = [info objectForKey:UIImagePickerControllerReferenceURL];
if(!assertURL) {
completion();
break;
}
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library assetForURL:assertURL resultBlock:^(ALAsset *asset) {
NSDictionary *metadata = asset.defaultRepresentation.metadata;
NSMutableDictionary *imageMetadata = [[NSMutableDictionary alloc] initWithDictionary:metadata];
[info setValue:imageMetadata forKey:UIImagePickerControllerMediaMetadata];
self.metadata = imageMetadata;
/*NSMutableDictionary *EXIFDictionary = [[imageMetadata objectForKey:(NSString*)kCGImagePropertyExifDictionary]mutableCopy];
if (EXIFDictionary) {
self.metadata = [[NSMutableDictionary alloc] init];
[self.metadata setObject:EXIFDictionary forKey:(NSString*)kCGImagePropertyExifDictionary];
}

if (IsAtLeastiOSVersion(@"8.0")) {
[[self locationManager] performSelector:NSSelectorFromString(@"requestWhenInUseAuthorization") withObject:nil afterDelay:0];
}
[[self locationManager] startUpdatingLocation];
NSMutableDictionary *GPSDictionary = [[imageMetadata objectForKey:(NSString*)kCGImagePropertyGPSDictionary] mutableCopy];
if(GPSDictionary) {
if(!self.metadata) {
self.metadata = [[NSMutableDictionary alloc] init];
}
[self.metadata setObject:GPSDictionary forKey:(NSString*)kCGImagePropertyGPSDictionary];
}*/
completion();
} failureBlock:^(NSError *error) {
completion();
NSLog(@"error %@", [error description]);
}];
}
// only when source type is UIImagePickerControllerSourceTypeCamera
if (options.usesGeolocation && options.sourceType == UIImagePickerControllerSourceTypeCamera) {
if (IsAtLeastiOSVersion(@"8.0")) {
[[self locationManager] performSelector:NSSelectorFromString(@"requestWhenInUseAuthorization") withObject:nil afterDelay:0];
}
[[self locationManager] startUpdatingLocation];
}
}
break;
default:
completion();
break;
};

return data;
}

- (NSString*)tempFilePath:(NSString*)extension
Expand Down Expand Up @@ -436,7 +462,6 @@ - (UIImage*)retrieveImage:(NSDictionary*)info options:(CDVPictureOptions*)option

- (void)resultForImage:(CDVPictureOptions*)options info:(NSDictionary*)info completion:(void (^)(CDVPluginResult* res))completion
{
CDVPluginResult* result = nil;
BOOL saveToPhotoAlbum = options.saveToPhotoAlbum;
UIImage* image = nil;

Expand All @@ -463,36 +488,36 @@ - (void)resultForImage:(CDVPictureOptions*)options info:(NSDictionary*)info comp
return;
} else {
NSString* nativeUri = [[self urlTransformer:url] absoluteString];
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:nativeUri];
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:nativeUri];
completion(result);
}
}
break;
case DestinationTypeFileUri:
{
image = [self retrieveImage:info options:options];
NSData* data = [self processImage:image info:info options:options];
if (data) {

[self processImage:image info:info options:options completion:^{
NSString* extension = options.encodingType == EncodingTypePNG? @"png" : @"jpg";
NSString* filePath = [self tempFilePath:extension];
NSError* err = nil;

CDVPluginResult* result = nil;
// save file
if (![data writeToFile:filePath options:NSAtomicWrite error:&err]) {
if (![[self imageDataWithMetaData] 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]];
}
}
completion(result);
}];
}
break;
case DestinationTypeDataUrl:
{
image = [self retrieveImage:info options:options];
NSData* data = [self processImage:image info:info options:options];
if (data) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:toBase64(data)];
}
[self processImage:image info:info options:options completion:^{
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:toBase64([self imageDataWithMetaData])];
completion(result);
}];
}
break;
default:
Expand All @@ -503,8 +528,6 @@ - (void)resultForImage:(CDVPictureOptions*)options info:(NSDictionary*)info comp
ALAssetsLibrary* library = [ALAssetsLibrary new];
[library writeImageToSavedPhotosAlbum:image.CGImage orientation:(ALAssetOrientation)(image.imageOrientation) completionBlock:nil];
}

completion(result);
}

- (CDVPluginResult*)resultForVideo:(NSDictionary*)info
Expand All @@ -513,6 +536,7 @@ - (CDVPluginResult*)resultForVideo:(NSDictionary*)info
return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:moviePath];
}

// Modified by Leckie. 2016-08-10
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info
{
__weak CDVCameraPicker* cameraPicker = (CDVCameraPicker*)picker;
Expand All @@ -524,7 +548,8 @@ - (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingM
NSString* mediaType = [info objectForKey:UIImagePickerControllerMediaType];
if ([mediaType isEqualToString:(NSString*)kUTTypeImage]) {
[weakSelf resultForImage:cameraPicker.pictureOptions info:info completion:^(CDVPluginResult* res) {
if (![self usesGeolocation] || picker.sourceType != UIImagePickerControllerSourceTypeCamera) {
if(!(self.pickerController.pictureOptions.usesGeolocation && self.pickerController.pictureOptions.sourceType == UIImagePickerControllerSourceTypeCamera)) {
// If usesGeolocation and sourceType=Camera will sendPluginResult later. In imagePickerControllerReturnImageResult.
[weakSelf.commandDelegate sendPluginResult:res callbackId:cameraPicker.callbackId];
weakSelf.hasPendingOperation = NO;
weakSelf.pickerController = nil;
Expand Down Expand Up @@ -664,22 +689,28 @@ - (void)locationManager:(CLLocationManager*)manager didFailWithError:(NSError*)e
[self imagePickerControllerReturnImageResult];
}

- (void)imagePickerControllerReturnImageResult
{
CDVPictureOptions* options = self.pickerController.pictureOptions;
CDVPluginResult* result = nil;

// Added by Leckie. 2016-08-12
- (NSData*)imageDataWithMetaData {
NSData *data = [self.data mutableCopy];
if (self.metadata) {
CGImageSourceRef sourceImage = CGImageSourceCreateWithData((__bridge CFDataRef)self.data, NULL);
CFStringRef sourceType = CGImageSourceGetType(sourceImage);

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

CFRelease(sourceImage);
CFRelease(destinationImage);
}
NSLog(@"metadata: %@", self.metadata);
return data;
}

- (void)imagePickerControllerReturnImageResult
{
CDVPictureOptions* options = self.pickerController.pictureOptions;
CDVPluginResult* result = nil;

switch (options.destinationType) {
case DestinationTypeFileUri:
Expand All @@ -689,7 +720,7 @@ - (void)imagePickerControllerReturnImageResult
NSString* filePath = [self tempFilePath:extension];

// save file
if (![self.data writeToFile:filePath options:NSAtomicWrite error:&err]) {
if (![[self imageDataWithMetaData] writeToFile:filePath options:NSAtomicWrite error:&err]) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
}
else {
Expand All @@ -699,7 +730,7 @@ - (void)imagePickerControllerReturnImageResult
break;
case DestinationTypeDataUrl:
{
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:toBase64(self.data)];
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:toBase64([self imageDataWithMetaData])];
}
break;
case DestinationTypeNativeUri:
Expand All @@ -716,10 +747,12 @@ - (void)imagePickerControllerReturnImageResult
self.data = nil;
self.metadata = nil;

// This is no use. In fact.
/*
if (options.saveToPhotoAlbum) {
ALAssetsLibrary *library = [ALAssetsLibrary new];
[library writeImageDataToSavedPhotosAlbum:self.data metadata:self.metadata completionBlock:nil];
}
}*/
}

@end
Expand Down
21 changes: 12 additions & 9 deletions tests/ios/CDVCameraTest/CDVCameraLibTests/CameraTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ @interface CameraTest : XCTestCase
@interface CDVCamera ()

// expose private interface
- (NSData*)processImage:(UIImage*)image info:(NSDictionary*)info options:(CDVPictureOptions*)options;
- (void)processImage:(UIImage*)image info:(NSDictionary*)info options:(CDVPictureOptions*)options completion:(void (^)())completion;
- (UIImage*)retrieveImage:(NSDictionary*)info options:(CDVPictureOptions*)options;
- (CDVPluginResult*)resultForImage:(CDVPictureOptions*)options info:(NSDictionary*)info;
- (CDVPluginResult*)resultForVideo:(NSDictionary*)info;
Expand Down Expand Up @@ -464,7 +464,6 @@ - (void) testRetrieveImage
- (void) testProcessImage
{
CDVPictureOptions* pictureOptions = [[CDVPictureOptions alloc] init];
NSData* resultData;

UIImage* originalImage = [self createImage:CGRectMake(0, 0, 1024, 768) orientation:UIImageOrientationDown];
NSData* originalImageDataPNG = UIImagePNGRepresentation(originalImage);
Expand All @@ -478,9 +477,10 @@ - (void) testProcessImage
pictureOptions.correctOrientation = NO;
pictureOptions.encodingType = EncodingTypePNG;

resultData = [self.plugin processImage:originalImage info:@{} options:pictureOptions];
XCTAssertEqualObjects([resultData base64EncodedStringWithOptions:0], [originalImageDataPNG base64EncodedStringWithOptions:0]);

[self.plugin processImage:originalImage info:@{} options:pictureOptions completion:^{
XCTAssertEqualObjects([self.plugin.data base64EncodedStringWithOptions:0], [originalImageDataPNG base64EncodedStringWithOptions:0]);
}];

// Original, JPEG, full quality

pictureOptions.allowsEditing = NO;
Expand All @@ -489,8 +489,10 @@ - (void) testProcessImage
pictureOptions.correctOrientation = NO;
pictureOptions.encodingType = EncodingTypeJPEG;

resultData = [self.plugin processImage:originalImage info:@{} options:pictureOptions];
XCTAssertEqualObjects([resultData base64EncodedStringWithOptions:0], [originalImageDataJPEG base64EncodedStringWithOptions:0]);
[self.plugin processImage:originalImage info:@{} options:pictureOptions completion:^{
XCTAssertEqualObjects([self.plugin.data base64EncodedStringWithOptions:0], [originalImageDataJPEG base64EncodedStringWithOptions:0]);
}];


// Original, JPEG, with quality value

Expand All @@ -502,8 +504,9 @@ - (void) testProcessImage
pictureOptions.quality = @(57);

NSData* originalImageDataJPEGWithQuality = UIImageJPEGRepresentation(originalImage, [pictureOptions.quality floatValue]/ 100.f);
resultData = [self.plugin processImage:originalImage info:@{} options:pictureOptions];
XCTAssertEqualObjects([resultData base64EncodedStringWithOptions:0], [originalImageDataJPEGWithQuality base64EncodedStringWithOptions:0]);
[self.plugin processImage:originalImage info:@{} options:pictureOptions completion:^{
XCTAssertEqualObjects([self.plugin.data base64EncodedStringWithOptions:0], [originalImageDataJPEGWithQuality base64EncodedStringWithOptions:0]);
}];

// TODO: usesGeolocation is not tested
}
Expand Down