From 35c653d24d3961655b3dcbb4c032942736754a92 Mon Sep 17 00:00:00 2001 From: Shazron Abdullah Date: Wed, 5 Nov 2014 12:19:42 -0800 Subject: [PATCH] CB-7937 - Re-factor iOS Camera plugin so that it is testable (closes #52) Signed-off-by: Shazron Abdullah --- plugin.xml | 2 + src/ios/CDVCamera.h | 44 +- src/ios/CDVCamera.m | 819 +++++++++++-------------- src/ios/UIImage+CropScaleOrientation.h | 28 + src/ios/UIImage+CropScaleOrientation.m | 170 +++++ 5 files changed, 597 insertions(+), 466 deletions(-) create mode 100644 src/ios/UIImage+CropScaleOrientation.h create mode 100644 src/ios/UIImage+CropScaleOrientation.m diff --git a/plugin.xml b/plugin.xml index d84c44d..2844e1f 100644 --- a/plugin.xml +++ b/plugin.xml @@ -133,6 +133,8 @@ + + diff --git a/src/ios/CDVCamera.h b/src/ios/CDVCamera.h index c2a71ac..231e9f6 100644 --- a/src/ios/CDVCamera.h +++ b/src/ios/CDVCamera.h @@ -42,22 +42,39 @@ enum CDVMediaType { }; typedef NSUInteger CDVMediaType; -@interface CDVCameraPicker : UIImagePickerController -{} +@interface CDVPictureOptions : NSObject -@property (assign) NSInteger quality; -@property (copy) NSString* callbackId; -@property (copy) NSString* postUrl; -@property (nonatomic) enum CDVDestinationType returnType; -@property (nonatomic) enum CDVEncodingType encodingType; -@property (strong) UIPopoverController* popoverController; +@property (strong) NSNumber* quality; +@property (assign) CDVDestinationType destinationType; +@property (assign) UIImagePickerControllerSourceType sourceType; @property (assign) CGSize targetSize; -@property (assign) bool correctOrientation; -@property (assign) bool saveToPhotoAlbum; -@property (assign) bool cropToSize; -@property (strong) UIView* webView; +@property (assign) CDVEncodingType encodingType; +@property (assign) CDVMediaType mediaType; +@property (assign) BOOL allowsEditing; +@property (assign) BOOL correctOrientation; +@property (assign) BOOL saveToPhotoAlbum; +@property (strong) NSDictionary* popoverOptions; +@property (assign) UIImagePickerControllerCameraDevice cameraDirection; + @property (assign) BOOL popoverSupported; @property (assign) BOOL usesGeolocation; +@property (assign) BOOL cropToSize; + ++ (instancetype) createFromTakePictureArguments:(NSArray*)arguments; + +@end + +@interface CDVCameraPicker : UIImagePickerController + +@property (strong) CDVPictureOptions* pictureOptions; + +@property (copy) NSString* callbackId; +@property (copy) NSString* postUrl; +@property (strong) UIPopoverController* pickerPopoverController; +@property (assign) BOOL cropToSize; +@property (strong) UIView* webView; + ++ (instancetype) createFromPictureOptions:(CDVPictureOptions*)options; @end @@ -92,9 +109,6 @@ typedef NSUInteger CDVMediaType; - (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo; - (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker; - (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated; -- (UIImage*)imageByScalingAndCroppingForSize:(UIImage*)anImage toSize:(CGSize)targetSize; -- (UIImage*)imageByScalingNotCroppingForSize:(UIImage*)anImage toSize:(CGSize)frameSize; -- (UIImage*)imageCorrectedForCaptureOrientation:(UIImage*)anImage; - (void)locationManager:(CLLocationManager*)manager didUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*)oldLocation; - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error; diff --git a/src/ios/CDVCamera.m b/src/ios/CDVCamera.m index 301187a..857c9b1 100644 --- a/src/ios/CDVCamera.m +++ b/src/ios/CDVCamera.m @@ -19,6 +19,7 @@ #import "CDVCamera.h" #import "CDVJpegHeaderWriter.h" +#import "UIImage+CropScaleOrientation.h" #import #import #import @@ -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 diff --git a/src/ios/UIImage+CropScaleOrientation.h b/src/ios/UIImage+CropScaleOrientation.h new file mode 100644 index 0000000..0f7fca5 --- /dev/null +++ b/src/ios/UIImage+CropScaleOrientation.h @@ -0,0 +1,28 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import + +@interface UIImage (CropScaleOrientation) + +- (UIImage*)imageByScalingAndCroppingForSize:(CGSize)targetSize; +- (UIImage*)imageCorrectedForCaptureOrientation; +- (UIImage*)imageByScalingNotCroppingForSize:(CGSize)targetSize; + +@end \ No newline at end of file diff --git a/src/ios/UIImage+CropScaleOrientation.m b/src/ios/UIImage+CropScaleOrientation.m new file mode 100644 index 0000000..3ab3ad5 --- /dev/null +++ b/src/ios/UIImage+CropScaleOrientation.m @@ -0,0 +1,170 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "UIImage+CropScaleOrientation.h" + +@implementation UIImage (CropScaleOrientation) + +- (UIImage*)imageByScalingAndCroppingForSize:(CGSize)targetSize +{ + UIImage* sourceImage = self; + 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 + } else { + scaleFactor = heightFactor; // scale to fit width + } + scaledWidth = width * scaleFactor; + scaledHeight = height * scaleFactor; + + // 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; +} + +- (UIImage*)imageCorrectedForCaptureOrientation +{ + float rotation_radians = 0; + bool perpendicular = false; + + switch ([self 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(self.size.width, self.size.height)); + CGContextRef context = UIGraphicsGetCurrentContext(); + + // Rotate around the center point + CGContextTranslateCTM(context, self.size.width / 2, self.size.height / 2); + CGContextRotateCTM(context, rotation_radians); + + CGContextScaleCTM(context, 1.0, -1.0); + float width = perpendicular ? self.size.height : self.size.width; + float height = perpendicular ? self.size.width : self.size.height; + CGContextDrawImage(context, CGRectMake(-width / 2, -height / 2, width, height), [self CGImage]); + + // Move the origin back since the rotation might've change it (if its 90 degrees) + if (perpendicular) { + CGContextTranslateCTM(context, -self.size.height / 2, -self.size.width / 2); + } + + UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return newImage; +} + +- (UIImage*)imageByScalingNotCroppingForSize:(CGSize)targetSize +{ + UIImage* sourceImage = self; + 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; + CGSize scaledSize = targetSize; + + if (CGSizeEqualToSize(imageSize, targetSize) == 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; +} + +@end