CB-7937 - Re-factor iOS Camera plugin so that it is testable (closes #52)

Signed-off-by: Shazron Abdullah <shazron@apache.org>
This commit is contained in:
Shazron Abdullah
2014-11-05 12:19:42 -08:00
parent 3ab650bdc9
commit 35c653d24d
5 changed files with 597 additions and 466 deletions
+368 -451
View File
@@ -19,6 +19,7 @@
#import "CDVCamera.h"
#import "CDVJpegHeaderWriter.h"
#import "UIImage+CropScaleOrientation.h"
#import <Cordova/NSArray+Comparisons.h>
#import <Cordova/NSData+Base64.h>
#import <Cordova/NSDictionary+Extensions.h>
@@ -34,6 +35,40 @@
static NSSet* org_apache_cordova_validArrowDirections;
@implementation CDVPictureOptions
+ (instancetype) createFromTakePictureArguments:(NSArray*)arguments
{
CDVPictureOptions* pictureOptions = [[CDVPictureOptions alloc] init];
pictureOptions.quality = [arguments objectAtIndex:0 withDefault:@(50)];
pictureOptions.destinationType = [[arguments objectAtIndex:1 withDefault:@(DestinationTypeFileUri)] unsignedIntegerValue];
pictureOptions.sourceType = [[arguments objectAtIndex:2 withDefault:@(UIImagePickerControllerSourceTypeCamera)] unsignedIntegerValue];
NSNumber* targetWidth = [arguments objectAtIndex:3 withDefault:nil];
NSNumber* targetHeight = [arguments objectAtIndex:4 withDefault:nil];
pictureOptions.targetSize = CGSizeMake(0, 0);
if ((targetWidth != nil) && (targetHeight != nil)) {
pictureOptions.targetSize = CGSizeMake([targetWidth floatValue], [targetHeight floatValue]);
}
pictureOptions.encodingType = [[arguments objectAtIndex:5 withDefault:@(EncodingTypeJPEG)] unsignedIntegerValue];
pictureOptions.mediaType = [[arguments objectAtIndex:6 withDefault:@(MediaTypePicture)] unsignedIntegerValue];
pictureOptions.allowsEditing = [[arguments objectAtIndex:7 withDefault:@(NO)] boolValue];
pictureOptions.correctOrientation = [[arguments objectAtIndex:8 withDefault:@(NO)] boolValue];
pictureOptions.saveToPhotoAlbum = [[arguments objectAtIndex:9 withDefault:@(NO)] boolValue];
pictureOptions.popoverOptions = [arguments objectAtIndex:10 withDefault:nil];
pictureOptions.cameraDirection = [[arguments objectAtIndex:11 withDefault:@(UIImagePickerControllerCameraDeviceRear)] unsignedIntegerValue];
pictureOptions.popoverSupported = NO;
pictureOptions.usesGeolocation = NO;
return pictureOptions;
}
@end
@interface CDVCamera ()
@property (readwrite, assign) BOOL hasPendingOperation;
@@ -49,7 +84,6 @@ static NSSet* org_apache_cordova_validArrowDirections;
@synthesize hasPendingOperation, pickerController, locationManager;
- (BOOL)usesGeolocation
{
id useGeo = [self.commandDelegate.settings objectForKey:[@"CameraUsesGeolocation" lowercaseString]];
@@ -62,106 +96,55 @@ static NSSet* org_apache_cordova_validArrowDirections;
(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
}
/* takePicture arguments:
* INDEX ARGUMENT
* 0 quality
* 1 destination type
* 2 source type
* 3 targetWidth
* 4 targetHeight
* 5 encodingType
* 6 mediaType
* 7 allowsEdit
* 8 correctOrientation
* 9 saveToPhotoAlbum
* 10 popoverOptions
* 11 cameraDirection
*/
- (void)takePicture:(CDVInvokedUrlCommand*)command
{
NSString* callbackId = command.callbackId;
NSArray* arguments = command.arguments;
self.hasPendingOperation = YES;
__weak CDVCamera* weakSelf = self;
NSString* sourceTypeString = [arguments objectAtIndex:2];
UIImagePickerControllerSourceType sourceType = UIImagePickerControllerSourceTypeCamera; // default
if (sourceTypeString != nil) {
sourceType = (UIImagePickerControllerSourceType)[sourceTypeString intValue];
}
bool hasCamera = [UIImagePickerController isSourceTypeAvailable:sourceType];
if (!hasCamera) {
NSLog(@"Camera.getPicture: source type %lu not available.", (unsigned long)sourceType);
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no camera available"];
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
return;
}
bool allowEdit = [[arguments objectAtIndex:7] boolValue];
NSNumber* targetWidth = [arguments objectAtIndex:3];
NSNumber* targetHeight = [arguments objectAtIndex:4];
NSNumber* mediaValue = [arguments objectAtIndex:6];
CDVMediaType mediaType = (mediaValue) ? [mediaValue intValue] : MediaTypePicture;
CGSize targetSize = CGSizeMake(0, 0);
if ((targetWidth != nil) && (targetHeight != nil)) {
targetSize = CGSizeMake([targetWidth floatValue], [targetHeight floatValue]);
}
// If a popover is already open, close it; we only want one at a time.
if (([[self pickerController] popoverController] != nil) && [[[self pickerController] popoverController] isPopoverVisible]) {
[[[self pickerController] popoverController] dismissPopoverAnimated:YES];
[[[self pickerController] popoverController] setDelegate:nil];
[[self pickerController] setPopoverController:nil];
}
CDVCameraPicker* cameraPicker = [[CDVCameraPicker alloc] init];
self.pickerController = cameraPicker;
cameraPicker.delegate = self;
cameraPicker.sourceType = sourceType;
cameraPicker.allowsEditing = allowEdit; // THIS IS ALL IT TAKES FOR CROPPING - jm
cameraPicker.callbackId = callbackId;
cameraPicker.targetSize = targetSize;
cameraPicker.cropToSize = NO;
// we need to capture this state for memory warnings that dealloc this object
cameraPicker.webView = self.webView;
cameraPicker.popoverSupported = [self popoverSupported];
cameraPicker.usesGeolocation = [self usesGeolocation];
cameraPicker.correctOrientation = [[arguments objectAtIndex:8] boolValue];
cameraPicker.saveToPhotoAlbum = [[arguments objectAtIndex:9] boolValue];
cameraPicker.encodingType = ([arguments objectAtIndex:5]) ? [[arguments objectAtIndex:5] intValue] : EncodingTypeJPEG;
cameraPicker.quality = ([arguments objectAtIndex:0]) ? [[arguments objectAtIndex:0] intValue] : 50;
cameraPicker.returnType = ([arguments objectAtIndex:1]) ? [[arguments objectAtIndex:1] intValue] : DestinationTypeFileUri;
if (sourceType == UIImagePickerControllerSourceTypeCamera) {
// We only allow taking pictures (no video) in this API.
cameraPicker.mediaTypes = [NSArray arrayWithObjects:(NSString*)kUTTypeImage, nil];
// We can only set the camera device if we're actually using the camera.
NSNumber* cameraDirection = [command argumentAtIndex:11 withDefault:[NSNumber numberWithInteger:UIImagePickerControllerCameraDeviceRear]];
cameraPicker.cameraDevice = (UIImagePickerControllerCameraDevice)[cameraDirection intValue];
} else if (mediaType == MediaTypeAll) {
cameraPicker.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:sourceType];
} else {
NSArray* mediaArray = [NSArray arrayWithObjects:(NSString*)(mediaType == MediaTypeVideo ? kUTTypeMovie : kUTTypeImage), nil];
cameraPicker.mediaTypes = mediaArray;
}
if ([self popoverSupported] && (sourceType != UIImagePickerControllerSourceTypeCamera)) {
if (cameraPicker.popoverController == nil) {
cameraPicker.popoverController = [[NSClassFromString(@"UIPopoverController")alloc] initWithContentViewController:cameraPicker];
[self.commandDelegate runInBackground:^{
CDVPictureOptions* pictureOptions = [CDVPictureOptions createFromTakePictureArguments:command.arguments];
pictureOptions.popoverSupported = [self popoverSupported];
pictureOptions.usesGeolocation = [self usesGeolocation];
pictureOptions.cropToSize = NO;
BOOL hasCamera = [UIImagePickerController isSourceTypeAvailable:pictureOptions.sourceType];
if (!hasCamera) {
NSLog(@"Camera.getPicture: source type %lu not available.", (unsigned long)pictureOptions.sourceType);
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No camera available"];
[weakSelf.commandDelegate sendPluginResult:result callbackId:command.callbackId];
return;
}
NSDictionary* options = [command.arguments objectAtIndex:10 withDefault:nil];
[self displayPopover:options];
} else {
[self.viewController presentViewController:cameraPicker animated:YES completion:nil];
}
self.hasPendingOperation = NO;
// If a popover is already open, close it; we only want one at a time.
if (([[weakSelf pickerController] pickerPopoverController] != nil) && [[[weakSelf pickerController] pickerPopoverController] isPopoverVisible]) {
[[[weakSelf pickerController] pickerPopoverController] dismissPopoverAnimated:YES];
[[[weakSelf pickerController] pickerPopoverController] setDelegate:nil];
[[weakSelf pickerController] setPickerPopoverController:nil];
}
CDVCameraPicker* cameraPicker = [CDVCameraPicker createFromPictureOptions:pictureOptions];
weakSelf.pickerController = cameraPicker;
cameraPicker.delegate = weakSelf;
cameraPicker.callbackId = command.callbackId;
// we need to capture this state for memory warnings that dealloc this object
cameraPicker.webView = weakSelf.webView;
if ([weakSelf popoverSupported] && (pictureOptions.sourceType != UIImagePickerControllerSourceTypeCamera)) {
if (cameraPicker.pickerPopoverController == nil) {
cameraPicker.pickerPopoverController = [[NSClassFromString(@"UIPopoverController") alloc] initWithContentViewController:cameraPicker];
}
[weakSelf displayPopover:pictureOptions.popoverOptions];
weakSelf.hasPendingOperation = NO;
} else {
[weakSelf.viewController presentViewController:cameraPicker animated:YES completion:^{
weakSelf.hasPendingOperation = NO;
}];
}
}];
}
- (void)repositionPopover:(CDVInvokedUrlCommand*)command
@@ -190,8 +173,8 @@ static NSSet* org_apache_cordova_validArrowDirections;
}
}
[[[self pickerController] popoverController] setDelegate:self];
[[[self pickerController] popoverController] presentPopoverFromRect:CGRectMake(x, y, width, height)
[[[self pickerController] pickerPopoverController] setDelegate:self];
[[[self pickerController] pickerPopoverController] presentPopoverFromRect:CGRectMake(x, y, width, height)
inView:[self.webView superview]
permittedArrowDirections:arrowDirection
animated:YES];
@@ -200,9 +183,9 @@ static NSSet* org_apache_cordova_validArrowDirections;
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if([navigationController isKindOfClass:[UIImagePickerController class]]){
UIImagePickerController * cameraPicker = (UIImagePickerController*)navigationController;
UIImagePickerController* cameraPicker = (UIImagePickerController*)navigationController;
if(![cameraPicker.mediaTypes containsObject:(NSString*) kUTTypeImage]){
if(![cameraPicker.mediaTypes containsObject:(NSString*)kUTTypeImage]){
[viewController.navigationItem setTitle:NSLocalizedString(@"Videos title", nil)];
}
}
@@ -245,13 +228,12 @@ static NSSet* org_apache_cordova_validArrowDirections;
- (void)popoverControllerDidDismissPopover:(id)popoverController
{
// [ self imagePickerControllerDidCancel:self.pickerController ]; '
UIPopoverController* pc = (UIPopoverController*)popoverController;
[pc dismissPopoverAnimated:YES];
pc.delegate = nil;
if (self.pickerController && self.pickerController.callbackId && self.pickerController.popoverController) {
self.pickerController.popoverController = nil;
if (self.pickerController && self.pickerController.callbackId && self.pickerController.pickerPopoverController) {
self.pickerController.pickerPopoverController = nil;
NSString* callbackId = self.pickerController.callbackId;
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no image selected"]; // error callback expects string ATM
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
@@ -259,122 +241,184 @@ static NSSet* org_apache_cordova_validArrowDirections;
self.hasPendingOperation = NO;
}
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info
- (NSData*)processImage:(UIImage*)image info:(NSDictionary*)info options:(CDVPictureOptions*)options
{
CDVCameraPicker* cameraPicker = (CDVCameraPicker*)picker;
if (cameraPicker.popoverSupported && (cameraPicker.popoverController != nil)) {
[cameraPicker.popoverController dismissPopoverAnimated:YES];
cameraPicker.popoverController.delegate = nil;
cameraPicker.popoverController = nil;
} else {
[[cameraPicker presentingViewController] dismissViewControllerAnimated:YES completion:nil];
}
CDVPluginResult* result = nil;
NSString* mediaType = [info objectForKey:UIImagePickerControllerMediaType];
// IMAGE TYPE
if ([mediaType isEqualToString:(NSString*)kUTTypeImage]) {
if (cameraPicker.returnType == DestinationTypeNativeUri) {
NSString* nativeUri = [(NSURL*)[info objectForKey:UIImagePickerControllerReferenceURL] absoluteString];
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:nativeUri];
} else {
// get the image
UIImage* image = nil;
if (cameraPicker.allowsEditing && [info objectForKey:UIImagePickerControllerEditedImage]) {
image = [info objectForKey:UIImagePickerControllerEditedImage];
} else {
image = [info objectForKey:UIImagePickerControllerOriginalImage];
}
if (cameraPicker.correctOrientation) {
image = [self imageCorrectedForCaptureOrientation:image];
}
UIImage* scaledImage = nil;
if ((cameraPicker.targetSize.width > 0) && (cameraPicker.targetSize.height > 0)) {
// if cropToSize, resize image and crop to target size, otherwise resize to fit target without cropping
if (cameraPicker.cropToSize) {
scaledImage = [self imageByScalingAndCroppingForSize:image toSize:cameraPicker.targetSize];
} else {
scaledImage = [self imageByScalingNotCroppingForSize:image toSize:cameraPicker.targetSize];
}
}
NSData* data = nil;
// returnedImage is the image that is returned to caller and (optionally) saved to photo album
UIImage* returnedImage = (scaledImage == nil ? image : scaledImage);
if (cameraPicker.encodingType == EncodingTypePNG) {
data = UIImagePNGRepresentation(returnedImage);
} else if ((cameraPicker.allowsEditing==false) && (cameraPicker.targetSize.width <= 0) && (cameraPicker.targetSize.height <= 0) && (cameraPicker.correctOrientation==false)){
NSData* data = nil;
switch (options.encodingType) {
case EncodingTypePNG:
data = UIImagePNGRepresentation(image);
break;
case EncodingTypeJPEG:
{
if ((options.allowsEditing == NO) && (options.targetSize.width <= 0) && (options.targetSize.height <= 0) && (options.correctOrientation == NO)){
// use image unedited as requested , don't resize
data = UIImageJPEGRepresentation(returnedImage, 1.0);
data = UIImageJPEGRepresentation(image, 1.0);
} else {
data = UIImageJPEGRepresentation(returnedImage, cameraPicker.quality / 100.0f);
if (cameraPicker.usesGeolocation) {
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];
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];
return;
}
} else {
data = UIImageJPEGRepresentation(image, [options.quality floatValue] / 100.0f);
}
}
if (cameraPicker.saveToPhotoAlbum) {
ALAssetsLibrary *library = [ALAssetsLibrary new];
[library writeImageToSavedPhotosAlbum:returnedImage.CGImage orientation:(ALAssetOrientation)(returnedImage.imageOrientation) completionBlock:nil];
}
}
break;
default:
break;
};
return data;
}
if (cameraPicker.returnType == DestinationTypeFileUri) {
// write to temp directory and return URI
// get the temp directory path
NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath];
- (NSString*)tempFilePath:(NSString*)extension
{
NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath];
NSFileManager* fileMgr = [[NSFileManager alloc] init]; // recommended by Apple (vs [NSFileManager defaultManager]) to be threadsafe
NSString* filePath;
// generate unique file name
int i = 1;
do {
filePath = [NSString stringWithFormat:@"%@/%@%03d.%@", docsPath, CDV_PHOTO_PREFIX, i++, extension];
} while ([fileMgr fileExistsAtPath:filePath]);
return filePath;
}
- (UIImage*)retrieveImage:(NSDictionary*)info options:(CDVPictureOptions*)options
{
// get the image
UIImage* image = nil;
if (options.allowsEditing && [info objectForKey:UIImagePickerControllerEditedImage]) {
image = [info objectForKey:UIImagePickerControllerEditedImage];
} else {
image = [info objectForKey:UIImagePickerControllerOriginalImage];
}
if (options.correctOrientation) {
image = [image imageCorrectedForCaptureOrientation];
}
UIImage* scaledImage = nil;
if ((options.targetSize.width > 0) && (options.targetSize.height > 0)) {
// if cropToSize, resize image and crop to target size, otherwise resize to fit target without cropping
if (options.cropToSize) {
scaledImage = [image imageByScalingAndCroppingForSize:options.targetSize];
} else {
scaledImage = [image imageByScalingNotCroppingForSize:options.targetSize];
}
}
return (scaledImage == nil ? image : scaledImage);
}
- (CDVPluginResult*)resultForImage:(CDVPictureOptions*)options info:(NSDictionary*)info
{
CDVPluginResult* result = nil;
BOOL saveToPhotoAlbum = options.saveToPhotoAlbum;
UIImage* image = nil;
switch (options.destinationType) {
case DestinationTypeNativeUri:
{
NSString* nativeUri = [(NSURL*)[info objectForKey:UIImagePickerControllerReferenceURL] absoluteString];
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:nativeUri];
saveToPhotoAlbum = NO;
}
break;
case DestinationTypeFileUri:
{
image = [self retrieveImage:info options:options];
NSData* data = [self processImage:image info:info options:options];
if (data) {
NSString* extension = options.encodingType == EncodingTypePNG? @"png" : @"jpg";
NSString* filePath = [self tempFilePath:extension];
NSError* err = nil;
NSFileManager* fileMgr = [[NSFileManager alloc] init]; // recommended by apple (vs [NSFileManager defaultManager]) to be threadsafe
// generate unique file name
NSString* filePath;
int i = 1;
do {
filePath = [NSString stringWithFormat:@"%@/%@%03d.%@", docsPath, CDV_PHOTO_PREFIX, i++, cameraPicker.encodingType == EncodingTypePNG ? @"png":@"jpg"];
} while ([fileMgr fileExistsAtPath:filePath]);
// 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:[[NSURL fileURLWithPath:filePath] absoluteString]];
}
} else {
}
}
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:[data base64EncodedString]];
}
}
break;
default:
break;
};
if (saveToPhotoAlbum && image) {
ALAssetsLibrary* library = [ALAssetsLibrary new];
[library writeImageToSavedPhotosAlbum:image.CGImage orientation:(ALAssetOrientation)(image.imageOrientation) completionBlock:nil];
}
// NOT IMAGE TYPE (MOVIE)
else {
NSString* moviePath = [[info objectForKey:UIImagePickerControllerMediaURL] absoluteString];
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:moviePath];
}
return result;
}
if (result) {
[self.commandDelegate sendPluginResult:result callbackId:cameraPicker.callbackId];
}
- (CDVPluginResult*)resultForVideo:(NSDictionary*)info
{
NSString* moviePath = [[info objectForKey:UIImagePickerControllerMediaURL] absoluteString];
return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:moviePath];
}
self.hasPendingOperation = NO;
self.pickerController = nil;
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info
{
__weak CDVCameraPicker* cameraPicker = (CDVCameraPicker*)picker;
__weak CDVCamera* weakSelf = self;
dispatch_block_t invoke = ^(void) {
__block CDVPluginResult* result = nil;
NSString* mediaType = [info objectForKey:UIImagePickerControllerMediaType];
if ([mediaType isEqualToString:(NSString*)kUTTypeImage]) {
result = [self resultForImage:cameraPicker.pictureOptions info:info];
}
else {
result = [self resultForVideo:info];
}
if (result) {
[weakSelf.commandDelegate sendPluginResult:result callbackId:cameraPicker.callbackId];
weakSelf.hasPendingOperation = NO;
weakSelf.pickerController = nil;
}
};
if (cameraPicker.pictureOptions.popoverSupported && (cameraPicker.pickerPopoverController != nil)) {
[cameraPicker.pickerPopoverController dismissPopoverAnimated:YES];
cameraPicker.pickerPopoverController.delegate = nil;
cameraPicker.pickerPopoverController = nil;
invoke();
} else {
[[cameraPicker presentingViewController] dismissViewControllerAnimated:YES completion:invoke];
}
}
// older api calls newer didFinishPickingMediaWithInfo
@@ -387,171 +431,28 @@ static NSSet* org_apache_cordova_validArrowDirections;
- (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker
{
CDVCameraPicker* cameraPicker = (CDVCameraPicker*)picker;
[[cameraPicker presentingViewController] dismissViewControllerAnimated:YES completion:nil];
CDVPluginResult* result;
if ([ALAssetsLibrary authorizationStatus] == ALAuthorizationStatusAuthorized) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no image selected"]; // error callback expects string ATM
} else {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"has no access to assets"]; // error callback expects string ATM
}
__weak CDVCameraPicker* cameraPicker = (CDVCameraPicker*)picker;
__weak CDVCamera* weakSelf = self;
[self.commandDelegate sendPluginResult:result callbackId:cameraPicker.callbackId];
self.hasPendingOperation = NO;
self.pickerController = nil;
}
- (UIImage*)imageByScalingAndCroppingForSize:(UIImage*)anImage toSize:(CGSize)targetSize
{
UIImage* sourceImage = anImage;
UIImage* newImage = nil;
CGSize imageSize = sourceImage.size;
CGFloat width = imageSize.width;
CGFloat height = imageSize.height;
CGFloat targetWidth = targetSize.width;
CGFloat targetHeight = targetSize.height;
CGFloat scaleFactor = 0.0;
CGFloat scaledWidth = targetWidth;
CGFloat scaledHeight = targetHeight;
CGPoint thumbnailPoint = CGPointMake(0.0, 0.0);
if (CGSizeEqualToSize(imageSize, targetSize) == NO) {
CGFloat widthFactor = targetWidth / width;
CGFloat heightFactor = targetHeight / height;
if (widthFactor > heightFactor) {
scaleFactor = widthFactor; // scale to fit height
dispatch_block_t invoke = ^ (void) {
CDVPluginResult* result;
if ([ALAssetsLibrary authorizationStatus] == ALAuthorizationStatusAuthorized) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no image selected"];
} else {
scaleFactor = heightFactor; // scale to fit width
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"has no access to assets"];
}
scaledWidth = width * scaleFactor;
scaledHeight = height * scaleFactor;
[weakSelf.commandDelegate sendPluginResult:result callbackId:cameraPicker.callbackId];
weakSelf.hasPendingOperation = NO;
weakSelf.pickerController = nil;
};
// center the image
if (widthFactor > heightFactor) {
thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5;
} else if (widthFactor < heightFactor) {
thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5;
}
}
UIGraphicsBeginImageContext(targetSize); // this will crop
CGRect thumbnailRect = CGRectZero;
thumbnailRect.origin = thumbnailPoint;
thumbnailRect.size.width = scaledWidth;
thumbnailRect.size.height = scaledHeight;
[sourceImage drawInRect:thumbnailRect];
newImage = UIGraphicsGetImageFromCurrentImageContext();
if (newImage == nil) {
NSLog(@"could not scale image");
}
// pop the context to get back to the default
UIGraphicsEndImageContext();
return newImage;
[[cameraPicker presentingViewController] dismissViewControllerAnimated:YES completion:invoke];
}
- (UIImage*)imageCorrectedForCaptureOrientation:(UIImage*)anImage
- (CLLocationManager*)locationManager
{
float rotation_radians = 0;
bool perpendicular = false;
switch ([anImage imageOrientation]) {
case UIImageOrientationUp :
rotation_radians = 0.0;
break;
case UIImageOrientationDown:
rotation_radians = M_PI; // don't be scared of radians, if you're reading this, you're good at math
break;
case UIImageOrientationRight:
rotation_radians = M_PI_2;
perpendicular = true;
break;
case UIImageOrientationLeft:
rotation_radians = -M_PI_2;
perpendicular = true;
break;
default:
break;
}
UIGraphicsBeginImageContext(CGSizeMake(anImage.size.width, anImage.size.height));
CGContextRef context = UIGraphicsGetCurrentContext();
// Rotate around the center point
CGContextTranslateCTM(context, anImage.size.width / 2, anImage.size.height / 2);
CGContextRotateCTM(context, rotation_radians);
CGContextScaleCTM(context, 1.0, -1.0);
float width = perpendicular ? anImage.size.height : anImage.size.width;
float height = perpendicular ? anImage.size.width : anImage.size.height;
CGContextDrawImage(context, CGRectMake(-width / 2, -height / 2, width, height), [anImage CGImage]);
// Move the origin back since the rotation might've change it (if its 90 degrees)
if (perpendicular) {
CGContextTranslateCTM(context, -anImage.size.height / 2, -anImage.size.width / 2);
}
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
- (UIImage*)imageByScalingNotCroppingForSize:(UIImage*)anImage toSize:(CGSize)frameSize
{
UIImage* sourceImage = anImage;
UIImage* newImage = nil;
CGSize imageSize = sourceImage.size;
CGFloat width = imageSize.width;
CGFloat height = imageSize.height;
CGFloat targetWidth = frameSize.width;
CGFloat targetHeight = frameSize.height;
CGFloat scaleFactor = 0.0;
CGSize scaledSize = frameSize;
if (CGSizeEqualToSize(imageSize, frameSize) == NO) {
CGFloat widthFactor = targetWidth / width;
CGFloat heightFactor = targetHeight / height;
// opposite comparison to imageByScalingAndCroppingForSize in order to contain the image within the given bounds
if (widthFactor > heightFactor) {
scaleFactor = heightFactor; // scale to fit height
} else {
scaleFactor = widthFactor; // scale to fit width
}
scaledSize = CGSizeMake(MIN(width * scaleFactor, targetWidth), MIN(height * scaleFactor, targetHeight));
}
// If the pixels are floats, it causes a white line in iOS8 and probably other versions too
scaledSize.width = (int)scaledSize.width;
scaledSize.height = (int)scaledSize.height;
UIGraphicsBeginImageContext(scaledSize); // this will resize
[sourceImage drawInRect:CGRectMake(0, 0, scaledSize.width, scaledSize.height)];
newImage = UIGraphicsGetImageFromCurrentImageContext();
if (newImage == nil) {
NSLog(@"could not scale image");
}
// pop the context to get back to the default
UIGraphicsEndImageContext();
return newImage;
}
- (CLLocationManager *)locationManager {
if (locationManager != nil) {
return locationManager;
}
@@ -565,70 +466,76 @@ static NSSet* org_apache_cordova_validArrowDirections;
- (void)locationManager:(CLLocationManager*)manager didUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*)oldLocation
{
if (locationManager != nil) {
[self.locationManager stopUpdatingLocation];
self.locationManager = nil;
NSMutableDictionary *GPSDictionary = [[NSMutableDictionary dictionary] init];
CLLocationDegrees latitude = newLocation.coordinate.latitude;
CLLocationDegrees longitude = newLocation.coordinate.longitude;
// latitude
if (latitude < 0.0) {
latitude = latitude * -1.0f;
[GPSDictionary setObject:@"S" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
} else {
[GPSDictionary setObject:@"N" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
}
[GPSDictionary setObject:[NSNumber numberWithFloat:latitude] forKey:(NSString*)kCGImagePropertyGPSLatitude];
// longitude
if (longitude < 0.0) {
longitude = longitude * -1.0f;
[GPSDictionary setObject:@"W" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
}
else {
[GPSDictionary setObject:@"E" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
}
[GPSDictionary setObject:[NSNumber numberWithFloat:longitude] forKey:(NSString*)kCGImagePropertyGPSLongitude];
// altitude
CGFloat altitude = newLocation.altitude;
if (!isnan(altitude)){
if (altitude < 0) {
altitude = -altitude;
[GPSDictionary setObject:@"1" forKey:(NSString *)kCGImagePropertyGPSAltitudeRef];
} else {
[GPSDictionary setObject:@"0" forKey:(NSString *)kCGImagePropertyGPSAltitudeRef];
}
[GPSDictionary setObject:[NSNumber numberWithFloat:altitude] forKey:(NSString *)kCGImagePropertyGPSAltitude];
if (locationManager == nil) {
return;
}
[self.locationManager stopUpdatingLocation];
self.locationManager = nil;
NSMutableDictionary *GPSDictionary = [[NSMutableDictionary dictionary] init];
CLLocationDegrees latitude = newLocation.coordinate.latitude;
CLLocationDegrees longitude = newLocation.coordinate.longitude;
// latitude
if (latitude < 0.0) {
latitude = latitude * -1.0f;
[GPSDictionary setObject:@"S" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
} else {
[GPSDictionary setObject:@"N" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
}
[GPSDictionary setObject:[NSNumber numberWithFloat:latitude] forKey:(NSString*)kCGImagePropertyGPSLatitude];
// longitude
if (longitude < 0.0) {
longitude = longitude * -1.0f;
[GPSDictionary setObject:@"W" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
}
else {
[GPSDictionary setObject:@"E" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
}
[GPSDictionary setObject:[NSNumber numberWithFloat:longitude] forKey:(NSString*)kCGImagePropertyGPSLongitude];
// altitude
CGFloat altitude = newLocation.altitude;
if (!isnan(altitude)){
if (altitude < 0) {
altitude = -altitude;
[GPSDictionary setObject:@"1" forKey:(NSString *)kCGImagePropertyGPSAltitudeRef];
} else {
[GPSDictionary setObject:@"0" forKey:(NSString *)kCGImagePropertyGPSAltitudeRef];
}
// Time and date
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"HH:mm:ss.SSSSSS"];
[formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
[GPSDictionary setObject:[formatter stringFromDate:newLocation.timestamp] forKey:(NSString *)kCGImagePropertyGPSTimeStamp];
[formatter setDateFormat:@"yyyy:MM:dd"];
[GPSDictionary setObject:[formatter stringFromDate:newLocation.timestamp] forKey:(NSString *)kCGImagePropertyGPSDateStamp];
[self.metadata setObject:GPSDictionary forKey:(NSString *)kCGImagePropertyGPSDictionary];
[self imagePickerControllerReturnImageResult];
}
[GPSDictionary setObject:[NSNumber numberWithFloat:altitude] forKey:(NSString *)kCGImagePropertyGPSAltitude];
}
// Time and date
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"HH:mm:ss.SSSSSS"];
[formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
[GPSDictionary setObject:[formatter stringFromDate:newLocation.timestamp] forKey:(NSString *)kCGImagePropertyGPSTimeStamp];
[formatter setDateFormat:@"yyyy:MM:dd"];
[GPSDictionary setObject:[formatter stringFromDate:newLocation.timestamp] forKey:(NSString *)kCGImagePropertyGPSDateStamp];
[self.metadata setObject:GPSDictionary forKey:(NSString *)kCGImagePropertyGPSDictionary];
[self imagePickerControllerReturnImageResult];
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
if (locationManager != nil) {
[self.locationManager stopUpdatingLocation];
self.locationManager = nil;
[self imagePickerControllerReturnImageResult];
}
- (void)locationManager:(CLLocationManager*)manager didFailWithError:(NSError*)error
{
if (locationManager == nil) {
return;
}
[self.locationManager stopUpdatingLocation];
self.locationManager = nil;
[self imagePickerControllerReturnImageResult];
}
- (void)imagePickerControllerReturnImageResult
{
CDVPictureOptions* options = self.pickerController.pictureOptions;
CDVPluginResult* result = nil;
if (self.metadata) {
@@ -643,39 +550,31 @@ static NSSet* org_apache_cordova_validArrowDirections;
CFRelease(destinationImage);
}
if (self.pickerController.saveToPhotoAlbum) {
ALAssetsLibrary *library = [ALAssetsLibrary new];
[library writeImageDataToSavedPhotosAlbum:self.data metadata:self.metadata completionBlock:nil];
}
if (self.pickerController.returnType == DestinationTypeFileUri) {
// write to temp directory and return URI
// get the temp directory path
NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath];
NSError* err = nil;
NSFileManager* fileMgr = [[NSFileManager alloc] init]; // recommended by apple (vs [NSFileManager defaultManager]) to be threadsafe
// generate unique file name
NSString* filePath;
int i = 1;
do {
filePath = [NSString stringWithFormat:@"%@/%@%03d.%@", docsPath, CDV_PHOTO_PREFIX, i++, self.pickerController.encodingType == EncodingTypePNG ? @"png":@"jpg"];
} while ([fileMgr fileExistsAtPath:filePath]);
// save file
if (![self.data writeToFile:filePath options:NSAtomicWrite error:&err]) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
switch (options.destinationType) {
case DestinationTypeFileUri:
{
NSError* err = nil;
NSString* extension = self.pickerController.pictureOptions.encodingType == EncodingTypePNG ? @"png":@"jpg";
NSString* filePath = [self tempFilePath:extension];
// save file
if (![self.data writeToFile:filePath options:NSAtomicWrite error:&err]) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
}
else {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[NSURL fileURLWithPath:filePath] absoluteString]];
}
}
else {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[NSURL fileURLWithPath:filePath] absoluteString]];
break;
case DestinationTypeDataUrl:
{
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[self.data base64EncodedString]];
}
}
else {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[self.data base64EncodedString]];
}
if (result) {
[self.commandDelegate sendPluginResult:result callbackId:self.pickerController.callbackId];
}
break;
case DestinationTypeNativeUri:
default:
break;
};
if (result) {
[self.commandDelegate sendPluginResult:result callbackId:self.pickerController.callbackId];
@@ -685,33 +584,29 @@ static NSSet* org_apache_cordova_validArrowDirections;
self.pickerController = nil;
self.data = nil;
self.metadata = nil;
if (options.saveToPhotoAlbum) {
ALAssetsLibrary *library = [ALAssetsLibrary new];
[library writeImageDataToSavedPhotosAlbum:self.data metadata:self.metadata completionBlock:nil];
}
}
@end
@implementation CDVCameraPicker
@synthesize quality, postUrl;
@synthesize returnType;
@synthesize callbackId;
@synthesize popoverController;
@synthesize targetSize;
@synthesize correctOrientation;
@synthesize saveToPhotoAlbum;
@synthesize encodingType;
@synthesize cropToSize;
@synthesize webView;
@synthesize popoverSupported;
- (BOOL)prefersStatusBarHidden {
- (BOOL)prefersStatusBarHidden
{
return YES;
}
- (UIViewController*)childViewControllerForStatusBarHidden {
- (UIViewController*)childViewControllerForStatusBarHidden
{
return nil;
}
- (void)viewWillAppear:(BOOL)animated {
- (void)viewWillAppear:(BOOL)animated
{
SEL sel = NSSelectorFromString(@"setNeedsStatusBarAppearanceUpdate");
if ([self respondsToSelector:sel]) {
[self performSelector:sel withObject:nil afterDelay:0];
@@ -720,4 +615,26 @@ static NSSet* org_apache_cordova_validArrowDirections;
[super viewWillAppear:animated];
}
+ (instancetype) createFromPictureOptions:(CDVPictureOptions*)pictureOptions;
{
CDVCameraPicker* cameraPicker = [[CDVCameraPicker alloc] init];
cameraPicker.pictureOptions = pictureOptions;
cameraPicker.sourceType = pictureOptions.sourceType;
cameraPicker.allowsEditing = pictureOptions.allowsEditing;
if (cameraPicker.sourceType == UIImagePickerControllerSourceTypeCamera) {
// We only allow taking pictures (no video) in this API.
cameraPicker.mediaTypes = @[(NSString*)kUTTypeImage];
// We can only set the camera device if we're actually using the camera.
cameraPicker.cameraDevice = pictureOptions.cameraDirection;
} else if (pictureOptions.mediaType == MediaTypeAll) {
cameraPicker.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:cameraPicker.sourceType];
} else {
NSArray* mediaArray = @[(NSString*)(pictureOptions.mediaType == MediaTypeVideo ? kUTTypeMovie : kUTTypeImage)];
cameraPicker.mediaTypes = mediaArray;
}
return cameraPicker;
}
@end