implemented upload file image for new event

This commit is contained in:
arnaucode
2017-06-23 16:45:13 +02:00
parent baa74e1921
commit 82cceaff53
247 changed files with 51054 additions and 49 deletions

View File

@@ -0,0 +1,116 @@
/*
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 <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#import <CoreLocation/CLLocationManager.h>
#import <Cordova/CDVPlugin.h>
enum CDVDestinationType {
DestinationTypeDataUrl = 0,
DestinationTypeFileUri,
DestinationTypeNativeUri
};
typedef NSUInteger CDVDestinationType;
enum CDVEncodingType {
EncodingTypeJPEG = 0,
EncodingTypePNG
};
typedef NSUInteger CDVEncodingType;
enum CDVMediaType {
MediaTypePicture = 0,
MediaTypeVideo,
MediaTypeAll
};
typedef NSUInteger CDVMediaType;
@interface CDVPictureOptions : NSObject
@property (strong) NSNumber* quality;
@property (assign) CDVDestinationType destinationType;
@property (assign) UIImagePickerControllerSourceType sourceType;
@property (assign) CGSize targetSize;
@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:(CDVInvokedUrlCommand*)command;
@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
// ======================================================================= //
@interface CDVCamera : CDVPlugin <UIImagePickerControllerDelegate,
UINavigationControllerDelegate,
UIPopoverControllerDelegate,
CLLocationManagerDelegate>
{}
@property (strong) CDVCameraPicker* pickerController;
@property (strong) NSMutableDictionary *metadata;
@property (strong, nonatomic) CLLocationManager *locationManager;
@property (strong) NSData* data;
/*
* getPicture
*
* arguments:
* 1: this is the javascript function that will be called with the results, the first parameter passed to the
* javascript function is the picture as a Base64 encoded string
* 2: this is the javascript function to be called if there was an error
* options:
* quality: integer between 1 and 100
*/
- (void)takePicture:(CDVInvokedUrlCommand*)command;
- (void)cleanup:(CDVInvokedUrlCommand*)command;
- (void)repositionPopover:(CDVInvokedUrlCommand*)command;
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info;
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo;
- (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker;
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
- (void)locationManager:(CLLocationManager*)manager didUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*)oldLocation;
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error;
@end

View File

@@ -0,0 +1,771 @@
/*
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 "CDVCamera.h"
#import "CDVJpegHeaderWriter.h"
#import "UIImage+CropScaleOrientation.h"
#import <ImageIO/CGImageProperties.h>
#import <AssetsLibrary/ALAssetRepresentation.h>
#import <AssetsLibrary/AssetsLibrary.h>
#import <AVFoundation/AVFoundation.h>
#import <ImageIO/CGImageSource.h>
#import <ImageIO/CGImageProperties.h>
#import <ImageIO/CGImageDestination.h>
#import <MobileCoreServices/UTCoreTypes.h>
#import <objc/message.h>
#ifndef __CORDOVA_4_0_0
#import <Cordova/NSData+Base64.h>
#endif
#define CDV_PHOTO_PREFIX @"cdv_photo_"
static NSSet* org_apache_cordova_validArrowDirections;
static NSString* toBase64(NSData* data) {
SEL s1 = NSSelectorFromString(@"cdv_base64EncodedString");
SEL s2 = NSSelectorFromString(@"base64EncodedString");
SEL s3 = NSSelectorFromString(@"base64EncodedStringWithOptions:");
if ([data respondsToSelector:s1]) {
NSString* (*func)(id, SEL) = (void *)[data methodForSelector:s1];
return func(data, s1);
} else if ([data respondsToSelector:s2]) {
NSString* (*func)(id, SEL) = (void *)[data methodForSelector:s2];
return func(data, s2);
} else if ([data respondsToSelector:s3]) {
NSString* (*func)(id, SEL, NSUInteger) = (void *)[data methodForSelector:s3];
return func(data, s3, 0);
} else {
return nil;
}
}
@implementation CDVPictureOptions
+ (instancetype) createFromTakePictureArguments:(CDVInvokedUrlCommand*)command
{
CDVPictureOptions* pictureOptions = [[CDVPictureOptions alloc] init];
pictureOptions.quality = [command argumentAtIndex:0 withDefault:@(50)];
pictureOptions.destinationType = [[command argumentAtIndex:1 withDefault:@(DestinationTypeFileUri)] unsignedIntegerValue];
pictureOptions.sourceType = [[command argumentAtIndex:2 withDefault:@(UIImagePickerControllerSourceTypeCamera)] unsignedIntegerValue];
NSNumber* targetWidth = [command argumentAtIndex:3 withDefault:nil];
NSNumber* targetHeight = [command argumentAtIndex:4 withDefault:nil];
pictureOptions.targetSize = CGSizeMake(0, 0);
if ((targetWidth != nil) && (targetHeight != nil)) {
pictureOptions.targetSize = CGSizeMake([targetWidth floatValue], [targetHeight floatValue]);
}
pictureOptions.encodingType = [[command argumentAtIndex:5 withDefault:@(EncodingTypeJPEG)] unsignedIntegerValue];
pictureOptions.mediaType = [[command argumentAtIndex:6 withDefault:@(MediaTypePicture)] unsignedIntegerValue];
pictureOptions.allowsEditing = [[command argumentAtIndex:7 withDefault:@(NO)] boolValue];
pictureOptions.correctOrientation = [[command argumentAtIndex:8 withDefault:@(NO)] boolValue];
pictureOptions.saveToPhotoAlbum = [[command argumentAtIndex:9 withDefault:@(NO)] boolValue];
pictureOptions.popoverOptions = [command argumentAtIndex:10 withDefault:nil];
pictureOptions.cameraDirection = [[command argumentAtIndex:11 withDefault:@(UIImagePickerControllerCameraDeviceRear)] unsignedIntegerValue];
pictureOptions.popoverSupported = NO;
pictureOptions.usesGeolocation = NO;
return pictureOptions;
}
@end
@interface CDVCamera ()
@property (readwrite, assign) BOOL hasPendingOperation;
@end
@implementation CDVCamera
+ (void)initialize
{
org_apache_cordova_validArrowDirections = [[NSSet alloc] initWithObjects:[NSNumber numberWithInt:UIPopoverArrowDirectionUp], [NSNumber numberWithInt:UIPopoverArrowDirectionDown], [NSNumber numberWithInt:UIPopoverArrowDirectionLeft], [NSNumber numberWithInt:UIPopoverArrowDirectionRight], [NSNumber numberWithInt:UIPopoverArrowDirectionAny], nil];
}
@synthesize hasPendingOperation, pickerController, locationManager;
- (NSURL*) urlTransformer:(NSURL*)url
{
NSURL* urlToTransform = url;
// for backwards compatibility - we check if this property is there
SEL sel = NSSelectorFromString(@"urlTransformer");
if ([self.commandDelegate respondsToSelector:sel]) {
// grab the block from the commandDelegate
NSURL* (^urlTransformer)(NSURL*) = ((id(*)(id, SEL))objc_msgSend)(self.commandDelegate, sel);
// if block is not null, we call it
if (urlTransformer) {
urlToTransform = urlTransformer(url);
}
}
return urlToTransform;
}
- (BOOL)usesGeolocation
{
id useGeo = [self.commandDelegate.settings objectForKey:[@"CameraUsesGeolocation" lowercaseString]];
return [(NSNumber*)useGeo boolValue];
}
- (BOOL)popoverSupported
{
return (NSClassFromString(@"UIPopoverController") != nil) &&
(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
}
- (void)takePicture:(CDVInvokedUrlCommand*)command
{
self.hasPendingOperation = YES;
__weak CDVCamera* weakSelf = self;
[self.commandDelegate runInBackground:^{
CDVPictureOptions* pictureOptions = [CDVPictureOptions createFromTakePictureArguments:command];
pictureOptions.popoverSupported = [weakSelf popoverSupported];
pictureOptions.usesGeolocation = [weakSelf 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;
}
// Validate the app has permission to access the camera
if (pictureOptions.sourceType == UIImagePickerControllerSourceTypeCamera && [AVCaptureDevice respondsToSelector:@selector(authorizationStatusForMediaType:)]) {
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if (authStatus == AVAuthorizationStatusDenied ||
authStatus == AVAuthorizationStatusRestricted) {
// If iOS 8+, offer a link to the Settings app
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wtautological-pointer-compare"
NSString* settingsButton = (&UIApplicationOpenSettingsURLString != NULL)
? NSLocalizedString(@"Settings", nil)
: nil;
#pragma clang diagnostic pop
// Denied; show an alert
dispatch_async(dispatch_get_main_queue(), ^{
[[[UIAlertView alloc] initWithTitle:[[NSBundle mainBundle]
objectForInfoDictionaryKey:@"CFBundleDisplayName"]
message:NSLocalizedString(@"Access to the camera has been prohibited; please enable it in the Settings app to continue.", nil)
delegate:weakSelf
cancelButtonTitle:NSLocalizedString(@"OK", nil)
otherButtonTitles:settingsButton, nil] show];
});
}
}
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;
// Perform UI operations on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
// 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];
}
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;
}];
}
});
}];
}
// Delegate for camera permission UIAlertView
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
// If Settings button (on iOS 8), open the settings app
if (buttonIndex == 1) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wtautological-pointer-compare"
if (&UIApplicationOpenSettingsURLString != NULL) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
}
#pragma clang diagnostic pop
}
// Dismiss the view
[[self.pickerController presentingViewController] dismissViewControllerAnimated:YES completion:nil];
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"has no access to camera"]; // error callback expects string ATM
[self.commandDelegate sendPluginResult:result callbackId:self.pickerController.callbackId];
self.hasPendingOperation = NO;
self.pickerController = nil;
}
- (void)repositionPopover:(CDVInvokedUrlCommand*)command
{
if (([[self pickerController] pickerPopoverController] != nil) && [[[self pickerController] pickerPopoverController] isPopoverVisible]) {
[[[self pickerController] pickerPopoverController] dismissPopoverAnimated:NO];
NSDictionary* options = [command argumentAtIndex:0 withDefault:nil];
[self displayPopover:options];
}
}
- (NSInteger)integerValueForKey:(NSDictionary*)dict key:(NSString*)key defaultValue:(NSInteger)defaultValue
{
NSInteger value = defaultValue;
NSNumber* val = [dict valueForKey:key]; // value is an NSNumber
if (val != nil) {
value = [val integerValue];
}
return value;
}
- (void)displayPopover:(NSDictionary*)options
{
NSInteger x = 0;
NSInteger y = 32;
NSInteger width = 320;
NSInteger height = 480;
UIPopoverArrowDirection arrowDirection = UIPopoverArrowDirectionAny;
if (options) {
x = [self integerValueForKey:options key:@"x" defaultValue:0];
y = [self integerValueForKey:options key:@"y" defaultValue:32];
width = [self integerValueForKey:options key:@"width" defaultValue:320];
height = [self integerValueForKey:options key:@"height" defaultValue:480];
arrowDirection = [self integerValueForKey:options key:@"arrowDir" defaultValue:UIPopoverArrowDirectionAny];
if (![org_apache_cordova_validArrowDirections containsObject:[NSNumber numberWithUnsignedInteger:arrowDirection]]) {
arrowDirection = UIPopoverArrowDirectionAny;
}
}
[[[self pickerController] pickerPopoverController] setDelegate:self];
[[[self pickerController] pickerPopoverController] presentPopoverFromRect:CGRectMake(x, y, width, height)
inView:[self.webView superview]
permittedArrowDirections:arrowDirection
animated:YES];
}
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if([navigationController isKindOfClass:[UIImagePickerController class]]){
UIImagePickerController* cameraPicker = (UIImagePickerController*)navigationController;
if(![cameraPicker.mediaTypes containsObject:(NSString*)kUTTypeImage]){
[viewController.navigationItem setTitle:NSLocalizedString(@"Videos", nil)];
}
}
}
- (void)cleanup:(CDVInvokedUrlCommand*)command
{
// empty the tmp directory
NSFileManager* fileMgr = [[NSFileManager alloc] init];
NSError* err = nil;
BOOL hasErrors = NO;
// clear contents of NSTemporaryDirectory
NSString* tempDirectoryPath = NSTemporaryDirectory();
NSDirectoryEnumerator* directoryEnumerator = [fileMgr enumeratorAtPath:tempDirectoryPath];
NSString* fileName = nil;
BOOL result;
while ((fileName = [directoryEnumerator nextObject])) {
// only delete the files we created
if (![fileName hasPrefix:CDV_PHOTO_PREFIX]) {
continue;
}
NSString* filePath = [tempDirectoryPath stringByAppendingPathComponent:fileName];
result = [fileMgr removeItemAtPath:filePath error:&err];
if (!result && err) {
NSLog(@"Failed to delete: %@ (error: %@)", filePath, err);
hasErrors = YES;
}
}
CDVPluginResult* pluginResult;
if (hasErrors) {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:@"One or more files failed to be deleted."];
} else {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
}
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
- (void)popoverControllerDidDismissPopover:(id)popoverController
{
UIPopoverController* pc = (UIPopoverController*)popoverController;
[pc dismissPopoverAnimated:YES];
pc.delegate = 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];
}
self.hasPendingOperation = NO;
}
- (NSData*)processImage:(UIImage*)image info:(NSDictionary*)info options:(CDVPictureOptions*)options
{
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) && (([options.quality integerValue] == 100) || (options.sourceType != UIImagePickerControllerSourceTypeCamera))){
// use image unedited as requested , don't resize
data = UIImageJPEGRepresentation(image, 1.0);
} else {
data = UIImageJPEGRepresentation(image, [options.quality floatValue] / 100.0f);
}
if (options.usesGeolocation) {
NSDictionary* controllerMetadata = [info objectForKey:@"UIImagePickerControllerMediaMetadata"];
if (controllerMetadata) {
self.data = data;
self.metadata = [[NSMutableDictionary alloc] init];
NSMutableDictionary* EXIFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyExifDictionary]mutableCopy];
if (EXIFDictionary) {
[self.metadata setObject:EXIFDictionary forKey:(NSString*)kCGImagePropertyExifDictionary];
}
if (IsAtLeastiOSVersion(@"8.0")) {
[[self locationManager] performSelector:NSSelectorFromString(@"requestWhenInUseAuthorization") withObject:nil afterDelay:0];
}
[[self locationManager] startUpdatingLocation];
}
}
}
break;
default:
break;
};
return data;
}
- (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);
}
- (void)resultForImage:(CDVPictureOptions*)options info:(NSDictionary*)info completion:(void (^)(CDVPluginResult* res))completion
{
CDVPluginResult* result = nil;
BOOL saveToPhotoAlbum = options.saveToPhotoAlbum;
UIImage* image = nil;
switch (options.destinationType) {
case DestinationTypeNativeUri:
{
NSURL* url = [info objectForKey:UIImagePickerControllerReferenceURL];
saveToPhotoAlbum = NO;
// If, for example, we use sourceType = Camera, URL might be nil because image is stored in memory.
// In this case we must save image to device before obtaining an URI.
if (url == nil) {
image = [self retrieveImage:info options:options];
ALAssetsLibrary* library = [ALAssetsLibrary new];
[library writeImageToSavedPhotosAlbum:image.CGImage orientation:(ALAssetOrientation)(image.imageOrientation) completionBlock:^(NSURL *assetURL, NSError *error) {
CDVPluginResult* resultToReturn = nil;
if (error) {
resultToReturn = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]];
} else {
NSString* nativeUri = [[self urlTransformer:assetURL] absoluteString];
resultToReturn = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:nativeUri];
}
completion(resultToReturn);
}];
return;
} else {
NSString* nativeUri = [[self urlTransformer:url] absoluteString];
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:nativeUri];
}
}
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;
// save file
if (![data writeToFile:filePath options:NSAtomicWrite error:&err]) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
} else {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[self urlTransformer:[NSURL fileURLWithPath:filePath]] absoluteString]];
}
}
}
break;
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)];
}
}
break;
default:
break;
};
if (saveToPhotoAlbum && image) {
ALAssetsLibrary* library = [ALAssetsLibrary new];
[library writeImageToSavedPhotosAlbum:image.CGImage orientation:(ALAssetOrientation)(image.imageOrientation) completionBlock:nil];
}
completion(result);
}
- (CDVPluginResult*)resultForVideo:(NSDictionary*)info
{
NSString* moviePath = [[info objectForKey:UIImagePickerControllerMediaURL] absoluteString];
return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:moviePath];
}
- (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]) {
[weakSelf resultForImage:cameraPicker.pictureOptions info:info completion:^(CDVPluginResult* res) {
if (![self usesGeolocation] || picker.sourceType != UIImagePickerControllerSourceTypeCamera) {
[weakSelf.commandDelegate sendPluginResult:res callbackId:cameraPicker.callbackId];
weakSelf.hasPendingOperation = NO;
weakSelf.pickerController = nil;
}
}];
}
else {
result = [weakSelf resultForVideo:info];
[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
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo
{
NSDictionary* imageInfo = [NSDictionary dictionaryWithObject:image forKey:UIImagePickerControllerOriginalImage];
[self imagePickerController:picker didFinishPickingMediaWithInfo:imageInfo];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker
{
__weak CDVCameraPicker* cameraPicker = (CDVCameraPicker*)picker;
__weak CDVCamera* weakSelf = self;
dispatch_block_t invoke = ^ (void) {
CDVPluginResult* result;
if (picker.sourceType == UIImagePickerControllerSourceTypeCamera && [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] != ALAuthorizationStatusAuthorized) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"has no access to camera"];
} else if (picker.sourceType != UIImagePickerControllerSourceTypeCamera && [ALAssetsLibrary authorizationStatus] != ALAuthorizationStatusAuthorized) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"has no access to assets"];
} else {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no image selected"];
}
[weakSelf.commandDelegate sendPluginResult:result callbackId:cameraPicker.callbackId];
weakSelf.hasPendingOperation = NO;
weakSelf.pickerController = nil;
};
[[cameraPicker presentingViewController] dismissViewControllerAnimated:YES completion:invoke];
}
- (CLLocationManager*)locationManager
{
if (locationManager != nil) {
return locationManager;
}
locationManager = [[CLLocationManager alloc] init];
[locationManager setDesiredAccuracy:kCLLocationAccuracyNearestTenMeters];
[locationManager setDelegate:self];
return locationManager;
}
- (void)locationManager:(CLLocationManager*)manager didUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*)oldLocation
{
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];
}
[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) {
return;
}
[self.locationManager stopUpdatingLocation];
self.locationManager = nil;
[self imagePickerControllerReturnImageResult];
}
- (void)imagePickerControllerReturnImageResult
{
CDVPictureOptions* options = self.pickerController.pictureOptions;
CDVPluginResult* result = nil;
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);
CGImageDestinationAddImageFromSource(destinationImage, sourceImage, 0, (__bridge CFDictionaryRef)self.metadata);
CGImageDestinationFinalize(destinationImage);
CFRelease(sourceImage);
CFRelease(destinationImage);
}
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:[[self urlTransformer:[NSURL fileURLWithPath:filePath]] absoluteString]];
}
}
break;
case DestinationTypeDataUrl:
{
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:toBase64(self.data)];
}
break;
case DestinationTypeNativeUri:
default:
break;
};
if (result) {
[self.commandDelegate sendPluginResult:result callbackId:self.pickerController.callbackId];
}
self.hasPendingOperation = NO;
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
- (BOOL)prefersStatusBarHidden
{
return YES;
}
- (UIViewController*)childViewControllerForStatusBarHidden
{
return nil;
}
- (void)viewWillAppear:(BOOL)animated
{
SEL sel = NSSelectorFromString(@"setNeedsStatusBarAppearanceUpdate");
if ([self respondsToSelector:sel]) {
[self performSelector:sel withObject:nil afterDelay:0];
}
[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

View File

@@ -0,0 +1,43 @@
/*
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.
*/
#ifndef CordovaLib_ExifData_h
#define CordovaLib_ExifData_h
// exif data types
typedef enum exifDataTypes {
EDT_UBYTE = 1, // 8 bit unsigned integer
EDT_ASCII_STRING, // 8 bits containing 7 bit ASCII code, null terminated
EDT_USHORT, // 16 bit unsigned integer
EDT_ULONG, // 32 bit unsigned integer
EDT_URATIONAL, // 2 longs, first is numerator and second is denominator
EDT_SBYTE,
EDT_UNDEFINED, // 8 bits
EDT_SSHORT,
EDT_SLONG, // 32bit signed integer (2's complement)
EDT_SRATIONAL, // 2 SLONGS, first long is numerator, second is denominator
EDT_SINGLEFLOAT,
EDT_DOUBLEFLOAT
} ExifDataTypes;
// maps integer code for exif data types to width in bytes
static const int DataTypeToWidth[] = {1,1,2,4,8,1,1,2,4,8,4,8};
static const int RECURSE_HORIZON = 8;
#endif

View File

@@ -0,0 +1,62 @@
/*
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 <Foundation/Foundation.h>
@interface CDVJpegHeaderWriter : NSObject {
NSDictionary * SubIFDTagFormatDict;
NSDictionary * IFD0TagFormatDict;
}
- (NSData*) spliceExifBlockIntoJpeg: (NSData*) jpegdata
withExifBlock: (NSString*) exifstr;
- (NSString*) createExifAPP1 : (NSDictionary*) datadict;
- (NSString*) formattedHexStringFromDecimalNumber: (NSNumber*) numb
withPlaces: (NSNumber*) width;
- (NSString*) formatNumberWithLeadingZeroes: (NSNumber*) numb
withPlaces: (NSNumber*) places;
- (NSString*) decimalToUnsignedRational: (NSNumber*) numb
withResultNumerator: (NSNumber**) numerator
withResultDenominator: (NSNumber**) denominator;
- (void) continuedFraction: (double) val
withFractionList: (NSMutableArray*) fractionlist
withHorizon: (int) horizon;
//- (void) expandContinuedFraction: (NSArray*) fractionlist;
- (void) splitDouble: (double) val
withIntComponent: (int*) rightside
withFloatRemainder: (double*) leftside;
- (NSString*) formatRationalWithNumerator: (NSNumber*) numerator
withDenominator: (NSNumber*) denominator
asSigned: (Boolean) signedFlag;
- (NSString*) hexStringFromData : (NSData*) data;
- (NSNumber*) numericFromHexString : (NSString *) hexstring;
/*
- (void) readExifMetaData : (NSData*) imgdata;
- (void) spliceImageData : (NSData*) imgdata withExifData: (NSDictionary*) exifdata;
- (void) locateExifMetaData : (NSData*) imgdata;
- (NSString*) createExifAPP1 : (NSDictionary*) datadict;
- (void) createExifDataString : (NSDictionary*) datadict;
- (NSString*) createDataElement : (NSString*) element
withElementData: (NSString*) data
withExternalDataBlock: (NSDictionary*) memblock;
- (NSString*) hexStringFromData : (NSData*) data;
- (NSNumber*) numericFromHexString : (NSString *) hexstring;
*/
@end

View File

@@ -0,0 +1,547 @@
/*
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 "CDVJpegHeaderWriter.h"
#include "CDVExif.h"
/* macros for tag info shorthand:
tagno : tag number
typecode : data type
components : number of components
appendString (TAGINF_W_APPEND only) : string to append to data
Exif date data format include an extra 0x00 to the end of the data
*/
#define TAGINF(tagno, typecode, components) [NSArray arrayWithObjects: tagno, typecode, components, nil]
#define TAGINF_W_APPEND(tagno, typecode, components, appendString) [NSArray arrayWithObjects: tagno, typecode, components, appendString, nil]
const uint mJpegId = 0xffd8; // JPEG format marker
const uint mExifMarker = 0xffe1; // APP1 jpeg header marker
const uint mExif = 0x45786966; // ASCII 'Exif', first characters of valid exif header after size
const uint mMotorallaByteAlign = 0x4d4d; // 'MM', motorola byte align, msb first or 'sane'
const uint mIntelByteAlgin = 0x4949; // 'II', Intel byte align, lsb first or 'batshit crazy reverso world'
const uint mTiffLength = 0x2a; // after byte align bits, next to bits are 0x002a(MM) or 0x2a00(II), tiff version number
@implementation CDVJpegHeaderWriter
- (id) init {
self = [super init];
// supported tags for exif IFD
IFD0TagFormatDict = [[NSDictionary alloc] initWithObjectsAndKeys:
// TAGINF(@"010e", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"ImageDescription",
TAGINF_W_APPEND(@"0132", [NSNumber numberWithInt:EDT_ASCII_STRING], @20, @"00"), @"DateTime",
TAGINF(@"010f", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"Make",
TAGINF(@"0110", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"Model",
TAGINF(@"0131", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"Software",
TAGINF(@"011a", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"XResolution",
TAGINF(@"011b", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"YResolution",
// currently supplied outside of Exif data block by UIImagePickerControllerMediaMetadata, this is set manually in CDVCamera.m
/* TAGINF(@"0112", [NSNumber numberWithInt:EDT_USHORT], @1), @"Orientation",
// rest of the tags are supported by exif spec, but are not specified by UIImagePickerControllerMediaMedadata
// should camera hardware supply these values in future versions, or if they can be derived, ImageHeaderWriter will include them gracefully
TAGINF(@"0128", [NSNumber numberWithInt:EDT_USHORT], @1), @"ResolutionUnit",
TAGINF(@"013e", [NSNumber numberWithInt:EDT_URATIONAL], @2), @"WhitePoint",
TAGINF(@"013f", [NSNumber numberWithInt:EDT_URATIONAL], @6), @"PrimaryChromaticities",
TAGINF(@"0211", [NSNumber numberWithInt:EDT_URATIONAL], @3), @"YCbCrCoefficients",
TAGINF(@"0213", [NSNumber numberWithInt:EDT_USHORT], @1), @"YCbCrPositioning",
TAGINF(@"0214", [NSNumber numberWithInt:EDT_URATIONAL], @6), @"ReferenceBlackWhite",
TAGINF(@"8298", [NSNumber numberWithInt:EDT_URATIONAL], @0), @"Copyright",
// offset to exif subifd, we determine this dynamically based on the size of the main exif IFD
TAGINF(@"8769", [NSNumber numberWithInt:EDT_ULONG], @1), @"ExifOffset",*/
nil];
// supported tages for exif subIFD
SubIFDTagFormatDict = [[NSDictionary alloc] initWithObjectsAndKeys:
//TAGINF(@"9000", [NSNumber numberWithInt:], @), @"ExifVersion",
//TAGINF(@"9202",[NSNumber numberWithInt:EDT_URATIONAL],@1), @"ApertureValue",
//TAGINF(@"9203",[NSNumber numberWithInt:EDT_SRATIONAL],@1), @"BrightnessValue",
TAGINF(@"a001",[NSNumber numberWithInt:EDT_USHORT],@1), @"ColorSpace",
TAGINF_W_APPEND(@"9004",[NSNumber numberWithInt:EDT_ASCII_STRING],@20,@"00"), @"DateTimeDigitized",
TAGINF_W_APPEND(@"9003",[NSNumber numberWithInt:EDT_ASCII_STRING],@20,@"00"), @"DateTimeOriginal",
TAGINF(@"a402", [NSNumber numberWithInt:EDT_USHORT], @1), @"ExposureMode",
TAGINF(@"8822", [NSNumber numberWithInt:EDT_USHORT], @1), @"ExposureProgram",
//TAGINF(@"829a", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"ExposureTime",
//TAGINF(@"829d", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"FNumber",
TAGINF(@"9209", [NSNumber numberWithInt:EDT_USHORT], @1), @"Flash",
// FocalLengthIn35mmFilm
TAGINF(@"a405", [NSNumber numberWithInt:EDT_USHORT], @1), @"FocalLenIn35mmFilm",
//TAGINF(@"920a", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"FocalLength",
//TAGINF(@"8827", [NSNumber numberWithInt:EDT_USHORT], @2), @"ISOSpeedRatings",
TAGINF(@"9207", [NSNumber numberWithInt:EDT_USHORT],@1), @"MeteringMode",
// specific to compressed data
TAGINF(@"a002", [NSNumber numberWithInt:EDT_ULONG],@1), @"PixelXDimension",
TAGINF(@"a003", [NSNumber numberWithInt:EDT_ULONG],@1), @"PixelYDimension",
// data type undefined, but this is a DSC camera, so value is always 1, treat as ushort
TAGINF(@"a301", [NSNumber numberWithInt:EDT_USHORT],@1), @"SceneType",
TAGINF(@"a217",[NSNumber numberWithInt:EDT_USHORT],@1), @"SensingMethod",
//TAGINF(@"9201", [NSNumber numberWithInt:EDT_SRATIONAL], @1), @"ShutterSpeedValue",
// specifies location of main subject in scene (x,y,wdith,height) expressed before rotation processing
//TAGINF(@"9214", [NSNumber numberWithInt:EDT_USHORT], @4), @"SubjectArea",
TAGINF(@"a403", [NSNumber numberWithInt:EDT_USHORT], @1), @"WhiteBalance",
nil];
return self;
}
- (NSData*) spliceExifBlockIntoJpeg: (NSData*) jpegdata withExifBlock: (NSString*) exifstr {
CDVJpegHeaderWriter * exifWriter = [[CDVJpegHeaderWriter alloc] init];
NSMutableData * exifdata = [NSMutableData dataWithCapacity: [exifstr length]/2];
int idx;
for (idx = 0; idx+1 < [exifstr length]; idx+=2) {
NSRange range = NSMakeRange(idx, 2);
NSString* hexStr = [exifstr substringWithRange:range];
NSScanner* scanner = [NSScanner scannerWithString:hexStr];
unsigned int intValue;
[scanner scanHexInt:&intValue];
[exifdata appendBytes:&intValue length:1];
}
NSMutableData * ddata = [NSMutableData dataWithCapacity: [jpegdata length]];
NSMakeRange(0,4);
int loc = 0;
bool done = false;
// read the jpeg data until we encounter the app1==0xFFE1 marker
while (loc+1 < [jpegdata length]) {
NSData * blag = [jpegdata subdataWithRange: NSMakeRange(loc,2)];
if( [[blag description] isEqualToString : @"<ffe1>"]) {
// read the APP1 block size bits
NSString * the = [exifWriter hexStringFromData:[jpegdata subdataWithRange: NSMakeRange(loc+2,2)]];
NSNumber * app1width = [exifWriter numericFromHexString:the];
//consume the original app1 block
[ddata appendData:exifdata];
// advance our loc marker past app1
loc += [app1width intValue] + 2;
done = true;
} else {
if(!done) {
[ddata appendData:blag];
loc += 2;
} else {
break;
}
}
}
// copy the remaining data
[ddata appendData:[jpegdata subdataWithRange: NSMakeRange(loc,[jpegdata length]-loc)]];
return ddata;
}
/**
* Create the Exif data block as a hex string
* jpeg uses Application Markers (APP's) as markers for application data
* APP1 is the application marker reserved for exif data
*
* (NSDictionary*) datadict - with subdictionaries marked '{TIFF}' and '{EXIF}' as returned by imagePickerController with a valid
* didFinishPickingMediaWithInfo data dict, under key @"UIImagePickerControllerMediaMetadata"
*
* the following constructs a hex string to Exif specifications, and is therefore brittle
* altering the order of arguments to the string constructors, modifying field sizes or formats,
* and any other minor change will likely prevent the exif data from being read
*/
- (NSString*) createExifAPP1 : (NSDictionary*) datadict {
NSMutableString * app1; // holds finalized product
NSString * exifIFD; // exif information file directory
NSString * subExifIFD; // subexif information file directory
// FFE1 is the hex APP1 marker code, and will allow client apps to read the data
NSString * app1marker = @"ffe1";
// SSSS size, to be determined
// EXIF ascii characters followed by 2bytes of zeros
NSString * exifmarker = @"457869660000";
// Tiff header: 4d4d is motorolla byte align (big endian), 002a is hex for 42
NSString * tiffheader = @"4d4d002a";
//first IFD offset from the Tiff header to IFD0. Since we are writing it, we know it's address 0x08
NSString * ifd0offset = @"00000008";
// current offset to next data area
int currentDataOffset = 0;
//data labeled as TIFF in UIImagePickerControllerMediaMetaData is part of the EXIF IFD0 portion of APP1
exifIFD = [self createExifIFDFromDict: [datadict objectForKey:@"{TIFF}"] withFormatDict: IFD0TagFormatDict isIFD0:YES currentDataOffset:&currentDataOffset];
//data labeled as EXIF in UIImagePickerControllerMediaMetaData is part of the EXIF Sub IFD portion of APP1
subExifIFD = [self createExifIFDFromDict: [datadict objectForKey:@"{Exif}"] withFormatDict: SubIFDTagFormatDict isIFD0:NO currentDataOffset:&currentDataOffset];
/*
NSLog(@"SUB EXIF IFD %@ WITH SIZE: %d",exifIFD,[exifIFD length]);
NSLog(@"SUB EXIF IFD %@ WITH SIZE: %d",subExifIFD,[subExifIFD length]);
*/
// construct the complete app1 data block
app1 = [[NSMutableString alloc] initWithFormat: @"%@%04x%@%@%@%@%@",
app1marker,
(unsigned int)(16 + ([exifIFD length]/2) + ([subExifIFD length]/2)) /*16+[exifIFD length]/2*/,
exifmarker,
tiffheader,
ifd0offset,
exifIFD,
subExifIFD];
return app1;
}
// returns hex string representing a valid exif information file directory constructed from the datadict and formatdict
- (NSString*) createExifIFDFromDict : (NSDictionary*) datadict
withFormatDict : (NSDictionary*) formatdict
isIFD0 : (BOOL) ifd0flag
currentDataOffset : (int*) dataoffset {
NSArray * datakeys = [datadict allKeys]; // all known data keys
NSArray * knownkeys = [formatdict allKeys]; // only keys in knowkeys are considered for entry in this IFD
NSMutableArray * ifdblock = [[NSMutableArray alloc] initWithCapacity: [datadict count]]; // all ifd entries
NSMutableArray * ifddatablock = [[NSMutableArray alloc] initWithCapacity: [datadict count]]; // data block entries
// ifd0flag = NO; // ifd0 requires a special flag and has offset to next ifd appended to end
// iterate through known provided data keys
for (int i = 0; i < [datakeys count]; i++) {
NSString * key = [datakeys objectAtIndex:i];
// don't muck about with unknown keys
if ([knownkeys indexOfObject: key] != NSNotFound) {
// create new IFD entry
NSString * entry = [self createIFDElement: key
withFormat: [formatdict objectForKey:key]
withElementData: [datadict objectForKey:key]];
// create the IFD entry's data block
NSString * data = [self createIFDElementDataWithFormat: [formatdict objectForKey:key]
withData: [datadict objectForKey:key]];
if (entry) {
[ifdblock addObject:entry];
if(!data) {
[ifdblock addObject:@""];
} else {
[ifddatablock addObject:data];
}
}
}
}
NSMutableString * exifstr = [[NSMutableString alloc] initWithCapacity: [ifdblock count] * 24];
NSMutableString * dbstr = [[NSMutableString alloc] initWithCapacity: 100];
int addr=*dataoffset; // current offset/address in datablock
if (ifd0flag) {
// calculate offset to datablock based on ifd file entry count
addr += 14+(12*([ifddatablock count]+1)); // +1 for tag 0x8769, exifsubifd offset
} else {
// current offset + numSubIFDs (2-bytes) + 12*numSubIFDs + endMarker (4-bytes)
addr += 2+(12*[ifddatablock count])+4;
}
for (int i = 0; i < [ifdblock count]; i++) {
NSString * entry = [ifdblock objectAtIndex:i];
NSString * data = [ifddatablock objectAtIndex:i];
// check if the data fits into 4 bytes
if( [data length] <= 8) {
// concatenate the entry and the (4byte) data entry into the final IFD entry and append to exif ifd string
[exifstr appendFormat : @"%@%@", entry, data];
} else {
[exifstr appendFormat : @"%@%08x", entry, addr];
[dbstr appendFormat: @"%@", data];
addr+= [data length] / 2;
/*
NSLog(@"=====data-length[%i]=======",[data length]);
NSLog(@"addr-offset[%i]",addr);
NSLog(@"entry[%@]",entry);
NSLog(@"data[%@]",data);
*/
}
}
// calculate IFD0 terminal offset tags, currently ExifSubIFD
unsigned int entrycount = (unsigned int)[ifdblock count];
if (ifd0flag) {
// 18 accounts for 8769's width + offset to next ifd, 8 accounts for start of header
NSNumber * offset = [NSNumber numberWithUnsignedInteger:[exifstr length] / 2 + [dbstr length] / 2 + 18+8];
[self appendExifOffsetTagTo: exifstr
withOffset : offset];
entrycount++;
}
*dataoffset = addr;
return [[NSString alloc] initWithFormat: @"%04x%@%@%@",
entrycount,
exifstr,
@"00000000", // offset to next IFD, 0 since there is none
dbstr]; // lastly, the datablock
}
// Creates an exif formatted exif information file directory entry
- (NSString*) createIFDElement: (NSString*) elementName withFormat: (NSArray*) formtemplate withElementData: (NSString*) data {
//NSArray * fielddata = [formatdict objectForKey: elementName];// format data of desired field
if (formtemplate) {
// format string @"%@%@%@%@", tag number, data format, components, value
NSNumber * dataformat = [formtemplate objectAtIndex:1];
NSNumber * components = [formtemplate objectAtIndex:2];
if([components intValue] == 0) {
components = [NSNumber numberWithUnsignedInteger:[data length] * DataTypeToWidth[[dataformat intValue]-1]];
}
return [[NSString alloc] initWithFormat: @"%@%@%08x",
[formtemplate objectAtIndex:0], // the field code
[self formatNumberWithLeadingZeroes: dataformat withPlaces: @4], // the data type code
[components intValue]]; // number of components
}
return NULL;
}
/**
* appends exif IFD0 tag 8769 "ExifOffset" to the string provided
* (NSMutableString*) str - string you wish to append the 8769 tag to: APP1 or IFD0 hex data string
* // TAGINF(@"8769", [NSNumber numberWithInt:EDT_ULONG], @1), @"ExifOffset",
*/
- (void) appendExifOffsetTagTo: (NSMutableString*) str withOffset : (NSNumber*) offset {
NSArray * format = TAGINF(@"8769", [NSNumber numberWithInt:EDT_ULONG], @1);
NSString * entry = [self createIFDElement: @"ExifOffset"
withFormat: format
withElementData: [offset stringValue]];
NSString * data = [self createIFDElementDataWithFormat: format
withData: [offset stringValue]];
[str appendFormat:@"%@%@", entry, data];
}
// formats the Information File Directory Data to exif format
- (NSString*) createIFDElementDataWithFormat: (NSArray*) dataformat withData: (NSString*) data {
NSMutableString * datastr = nil;
NSNumber * tmp = nil;
NSNumber * formatcode = [dataformat objectAtIndex:1];
NSUInteger formatItemsCount = [dataformat count];
NSNumber * num = @0;
NSNumber * denom = @0;
switch ([formatcode intValue]) {
case EDT_UBYTE:
break;
case EDT_ASCII_STRING:
datastr = [[NSMutableString alloc] init];
for (int i = 0; i < [data length]; i++) {
[datastr appendFormat:@"%02x",[data characterAtIndex:i]];
}
if (formatItemsCount > 3) {
// We have additional data to append.
// currently used by Date format to append final 0x00 but can be used by other data types as well in the future
[datastr appendString:[dataformat objectAtIndex:3]];
}
if ([datastr length] < 8) {
NSString * format = [NSString stringWithFormat:@"%%0%dd", (int)(8 - [datastr length])];
[datastr appendFormat:format,0];
}
return datastr;
case EDT_USHORT:
return [[NSString alloc] initWithFormat : @"%@%@",
[self formattedHexStringFromDecimalNumber: [NSNumber numberWithInt: [data intValue]] withPlaces: @4],
@"0000"];
case EDT_ULONG:
tmp = [NSNumber numberWithUnsignedLong:[data intValue]];
return [NSString stringWithFormat : @"%@",
[self formattedHexStringFromDecimalNumber: tmp withPlaces: @8]];
case EDT_URATIONAL:
return [self decimalToUnsignedRational: [NSNumber numberWithDouble:[data doubleValue]]
withResultNumerator: &num
withResultDenominator: &denom];
case EDT_SBYTE:
break;
case EDT_UNDEFINED:
break; // 8 bits
case EDT_SSHORT:
break;
case EDT_SLONG:
break; // 32bit signed integer (2's complement)
case EDT_SRATIONAL:
break; // 2 SLONGS, first long is numerator, second is denominator
case EDT_SINGLEFLOAT:
break;
case EDT_DOUBLEFLOAT:
break;
}
return datastr;
}
//======================================================================================================================
// Utility Methods
//======================================================================================================================
// creates a formatted little endian hex string from a number and width specifier
- (NSString*) formattedHexStringFromDecimalNumber: (NSNumber*) numb withPlaces: (NSNumber*) width {
NSMutableString * str = [[NSMutableString alloc] initWithCapacity:[width intValue]];
NSString * formatstr = [[NSString alloc] initWithFormat: @"%%%@%dx", @"0", [width intValue]];
[str appendFormat:formatstr, [numb intValue]];
return str;
}
// format number as string with leading 0's
- (NSString*) formatNumberWithLeadingZeroes: (NSNumber *) numb withPlaces: (NSNumber *) places {
NSNumberFormatter * formatter = [[NSNumberFormatter alloc] init];
NSString *formatstr = [@"" stringByPaddingToLength:[places unsignedIntegerValue] withString:@"0" startingAtIndex:0];
[formatter setPositiveFormat:formatstr];
return [formatter stringFromNumber:numb];
}
// approximate a decimal with a rational by method of continued fraction
// can be collasped into decimalToUnsignedRational after testing
- (void) decimalToRational: (NSNumber *) numb
withResultNumerator: (NSNumber**) numerator
withResultDenominator: (NSNumber**) denominator {
NSMutableArray * fractionlist = [[NSMutableArray alloc] initWithCapacity:8];
[self continuedFraction: [numb doubleValue]
withFractionList: fractionlist
withHorizon: 8];
// simplify complex fraction represented by partial fraction list
[self expandContinuedFraction: fractionlist
withResultNumerator: numerator
withResultDenominator: denominator];
}
// approximate a decimal with an unsigned rational by method of continued fraction
- (NSString*) decimalToUnsignedRational: (NSNumber *) numb
withResultNumerator: (NSNumber**) numerator
withResultDenominator: (NSNumber**) denominator {
NSMutableArray * fractionlist = [[NSMutableArray alloc] initWithCapacity:8];
// generate partial fraction list
[self continuedFraction: [numb doubleValue]
withFractionList: fractionlist
withHorizon: 8];
// simplify complex fraction represented by partial fraction list
[self expandContinuedFraction: fractionlist
withResultNumerator: numerator
withResultDenominator: denominator];
return [self formatFractionList: fractionlist];
}
// recursive implementation of decimal approximation by continued fraction
- (void) continuedFraction: (double) val
withFractionList: (NSMutableArray*) fractionlist
withHorizon: (int) horizon {
int whole;
double remainder;
// 1. split term
[self splitDouble: val withIntComponent: &whole withFloatRemainder: &remainder];
[fractionlist addObject: [NSNumber numberWithInt:whole]];
// 2. calculate reciprocal of remainder
if (!remainder) return; // early exit, exact fraction found, avoids recip/0
double recip = 1 / remainder;
// 3. exit condition
if ([fractionlist count] > horizon) {
return;
}
// 4. recurse
[self continuedFraction:recip withFractionList: fractionlist withHorizon: horizon];
}
// expand continued fraction list, creating a single level rational approximation
-(void) expandContinuedFraction: (NSArray*) fractionlist
withResultNumerator: (NSNumber**) numerator
withResultDenominator: (NSNumber**) denominator {
NSUInteger i = 0;
int den = 0;
int num = 0;
if ([fractionlist count] == 1) {
*numerator = [NSNumber numberWithInt:[[fractionlist objectAtIndex:0] intValue]];
*denominator = @1;
return;
}
//begin at the end of the list
i = [fractionlist count] - 1;
num = 1;
den = [[fractionlist objectAtIndex:i] intValue];
while (i > 0) {
int t = [[fractionlist objectAtIndex: i-1] intValue];
num = t * den + num;
if (i==1) {
break;
} else {
t = num;
num = den;
den = t;
}
i--;
}
// set result parameters values
*numerator = [NSNumber numberWithInt: num];
*denominator = [NSNumber numberWithInt: den];
}
// formats expanded fraction list to string matching exif specification
- (NSString*) formatFractionList: (NSArray *) fractionlist {
NSMutableString * str = [[NSMutableString alloc] initWithCapacity:16];
if ([fractionlist count] == 1){
[str appendFormat: @"%08x00000001", [[fractionlist objectAtIndex:0] intValue]];
}
return str;
}
// format rational as
- (NSString*) formatRationalWithNumerator: (NSNumber*) numerator withDenominator: (NSNumber*) denominator asSigned: (Boolean) signedFlag {
NSMutableString * str = [[NSMutableString alloc] initWithCapacity:16];
if (signedFlag) {
long num = [numerator longValue];
long den = [denominator longValue];
[str appendFormat: @"%08lx%08lx", num >= 0 ? num : ~ABS(num) + 1, num >= 0 ? den : ~ABS(den) + 1];
} else {
[str appendFormat: @"%08lx%08lx", [numerator unsignedLongValue], [denominator unsignedLongValue]];
}
return str;
}
// split a floating point number into two integer values representing the left and right side of the decimal
- (void) splitDouble: (double) val withIntComponent: (int*) rightside withFloatRemainder: (double*) leftside {
*rightside = val; // convert numb to int representation, which truncates the decimal portion
*leftside = val - *rightside;
}
//
- (NSString*) hexStringFromData : (NSData*) data {
//overflow detection
const unsigned char *dataBuffer = [data bytes];
return [[NSString alloc] initWithFormat: @"%02x%02x",
(unsigned char)dataBuffer[0],
(unsigned char)dataBuffer[1]];
}
// convert a hex string to a number
- (NSNumber*) numericFromHexString : (NSString *) hexstring {
NSScanner * scan = NULL;
unsigned int numbuf= 0;
scan = [NSScanner scannerWithString:hexstring];
[scan scanHexInt:&numbuf];
return [NSNumber numberWithInt:numbuf];
}
@end

View File

@@ -0,0 +1,29 @@
/*
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 <UIKit/UIKit.h>
@interface UIImage (CropScaleOrientation)
- (UIImage*)imageByScalingAndCroppingForSize:(CGSize)targetSize;
- (UIImage*)imageCorrectedForCaptureOrientation;
- (UIImage*)imageCorrectedForCaptureOrientation:(UIImageOrientation)imageOrientation;
- (UIImage*)imageByScalingNotCroppingForSize:(CGSize)targetSize;
@end

View File

@@ -0,0 +1,175 @@
/*
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:(UIImageOrientation)imageOrientation
{
float rotation_radians = 0;
bool perpendicular = false;
switch (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*)imageCorrectedForCaptureOrientation
{
return [self imageCorrectedForCaptureOrientation:[self imageOrientation]];
}
- (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