add iOS classes to plugin
This commit is contained in:
parent
5455bbeb37
commit
fc2c9bd082
102
src/ios/CDVCamera.h
Normal file
102
src/ios/CDVCamera.h
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
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 "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 CDVCameraPicker : UIImagePickerController
|
||||||
|
{}
|
||||||
|
|
||||||
|
@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 (assign) CGSize targetSize;
|
||||||
|
@property (assign) bool correctOrientation;
|
||||||
|
@property (assign) bool saveToPhotoAlbum;
|
||||||
|
@property (assign) bool cropToSize;
|
||||||
|
@property (strong) UIWebView* webView;
|
||||||
|
@property (assign) BOOL popoverSupported;
|
||||||
|
|
||||||
|
@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)postImage:(UIImage*)anImage withFilename:(NSString*)filename toUrl:(NSURL*)url;
|
||||||
|
- (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;
|
||||||
|
- (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;
|
||||||
|
|
||||||
|
@end
|
729
src/ios/CDVCamera.m
Normal file
729
src/ios/CDVCamera.m
Normal file
@ -0,0 +1,729 @@
|
|||||||
|
/*
|
||||||
|
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 "NSArray+Comparisons.h"
|
||||||
|
#import "NSData+Base64.h"
|
||||||
|
#import "NSDictionary+Extensions.h"
|
||||||
|
#import <ImageIO/CGImageProperties.h>
|
||||||
|
#import <AssetsLibrary/ALAssetRepresentation.h>
|
||||||
|
#import <ImageIO/CGImageSource.h>
|
||||||
|
#import <ImageIO/CGImageProperties.h>
|
||||||
|
#import <ImageIO/CGImageDestination.h>
|
||||||
|
#import <MobileCoreServices/UTCoreTypes.h>
|
||||||
|
|
||||||
|
#define CDV_PHOTO_PREFIX @"cdv_photo_"
|
||||||
|
|
||||||
|
static NSSet* org_apache_cordova_validArrowDirections;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
|
- (BOOL)popoverSupported
|
||||||
|
{
|
||||||
|
return (NSClassFromString(@"UIPopoverController") != nil) &&
|
||||||
|
(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 = NO;
|
||||||
|
|
||||||
|
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 %d not available.", 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.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];
|
||||||
|
}
|
||||||
|
NSDictionary* options = [command.arguments objectAtIndex:10 withDefault:nil];
|
||||||
|
[self displayPopover:options];
|
||||||
|
} else {
|
||||||
|
if ([self.viewController respondsToSelector:@selector(presentViewController:::)]) {
|
||||||
|
[self.viewController presentViewController:cameraPicker animated:YES completion:nil];
|
||||||
|
} else {
|
||||||
|
[self.viewController presentModalViewController:cameraPicker animated:YES];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.hasPendingOperation = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)repositionPopover:(CDVInvokedUrlCommand*)command
|
||||||
|
{
|
||||||
|
NSDictionary* options = [command.arguments objectAtIndex:0 withDefault:nil];
|
||||||
|
|
||||||
|
[self displayPopover:options];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)displayPopover:(NSDictionary*)options
|
||||||
|
{
|
||||||
|
int x = 0;
|
||||||
|
int y = 32;
|
||||||
|
int width = 320;
|
||||||
|
int height = 480;
|
||||||
|
UIPopoverArrowDirection arrowDirection = UIPopoverArrowDirectionAny;
|
||||||
|
|
||||||
|
if (options) {
|
||||||
|
x = [options integerValueForKey:@"x" defaultValue:0];
|
||||||
|
y = [options integerValueForKey:@"y" defaultValue:32];
|
||||||
|
width = [options integerValueForKey:@"width" defaultValue:320];
|
||||||
|
height = [options integerValueForKey:@"height" defaultValue:480];
|
||||||
|
arrowDirection = [options integerValueForKey:@"arrowDir" defaultValue:UIPopoverArrowDirectionAny];
|
||||||
|
if (![org_apache_cordova_validArrowDirections containsObject:[NSNumber numberWithInt:arrowDirection]]) {
|
||||||
|
arrowDirection = UIPopoverArrowDirectionAny;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[[self pickerController] popoverController] setDelegate:self];
|
||||||
|
[[[self pickerController] popoverController] 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 title", 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
|
||||||
|
{
|
||||||
|
// [ 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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info
|
||||||
|
{
|
||||||
|
CDVCameraPicker* cameraPicker = (CDVCameraPicker*)picker;
|
||||||
|
|
||||||
|
if (cameraPicker.popoverSupported && (cameraPicker.popoverController != nil)) {
|
||||||
|
[cameraPicker.popoverController dismissPopoverAnimated:YES];
|
||||||
|
cameraPicker.popoverController.delegate = nil;
|
||||||
|
cameraPicker.popoverController = nil;
|
||||||
|
} else {
|
||||||
|
if ([cameraPicker respondsToSelector:@selector(presentingViewController)]) {
|
||||||
|
[[cameraPicker presentingViewController] dismissModalViewControllerAnimated:YES];
|
||||||
|
} else {
|
||||||
|
[[cameraPicker parentViewController] dismissModalViewControllerAnimated:YES];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (cameraPicker.encodingType == EncodingTypePNG) {
|
||||||
|
data = UIImagePNGRepresentation(scaledImage == nil ? image : scaledImage);
|
||||||
|
} else {
|
||||||
|
self.data = UIImageJPEGRepresentation(scaledImage == nil ? image : scaledImage, cameraPicker.quality / 100.0f);
|
||||||
|
|
||||||
|
NSDictionary *controllerMetadata = [info objectForKey:@"UIImagePickerControllerMediaMetadata"];
|
||||||
|
if (controllerMetadata) {
|
||||||
|
self.metadata = [[NSMutableDictionary alloc] init];
|
||||||
|
|
||||||
|
NSMutableDictionary *EXIFDictionary = [[controllerMetadata objectForKey:(NSString *)kCGImagePropertyExifDictionary]mutableCopy];
|
||||||
|
if (EXIFDictionary) [self.metadata setObject:EXIFDictionary forKey:(NSString *)kCGImagePropertyExifDictionary];
|
||||||
|
|
||||||
|
[[self locationManager] startUpdatingLocation];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cameraPicker.saveToPhotoAlbum) {
|
||||||
|
UIImageWriteToSavedPhotosAlbum([UIImage imageWithData:data], nil, nil, nil);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cameraPicker.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++, 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 {
|
||||||
|
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[data base64EncodedString]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// NOT IMAGE TYPE (MOVIE)
|
||||||
|
else {
|
||||||
|
NSString* moviePath = [[info objectForKey:UIImagePickerControllerMediaURL] absoluteString];
|
||||||
|
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:moviePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
[self.commandDelegate sendPluginResult:result callbackId:cameraPicker.callbackId];
|
||||||
|
}
|
||||||
|
|
||||||
|
self.hasPendingOperation = NO;
|
||||||
|
self.pickerController = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
CDVCameraPicker* cameraPicker = (CDVCameraPicker*)picker;
|
||||||
|
|
||||||
|
if ([cameraPicker respondsToSelector:@selector(presentingViewController)]) {
|
||||||
|
[[cameraPicker presentingViewController] dismissModalViewControllerAnimated:YES];
|
||||||
|
} else {
|
||||||
|
[[cameraPicker parentViewController] dismissModalViewControllerAnimated:YES];
|
||||||
|
}
|
||||||
|
// popoverControllerDidDismissPopover:(id)popoverController is called if popover is cancelled
|
||||||
|
|
||||||
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no image selected"]; // error callback expects string ATM
|
||||||
|
[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
|
||||||
|
} 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:(UIImage*)anImage
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)postImage:(UIImage*)anImage withFilename:(NSString*)filename toUrl:(NSURL*)url
|
||||||
|
{
|
||||||
|
self.hasPendingOperation = YES;
|
||||||
|
|
||||||
|
NSString* boundary = @"----BOUNDARY_IS_I";
|
||||||
|
|
||||||
|
NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url];
|
||||||
|
[req setHTTPMethod:@"POST"];
|
||||||
|
|
||||||
|
NSString* contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
|
||||||
|
[req setValue:contentType forHTTPHeaderField:@"Content-type"];
|
||||||
|
|
||||||
|
NSData* imageData = UIImagePNGRepresentation(anImage);
|
||||||
|
|
||||||
|
// adding the body
|
||||||
|
NSMutableData* postBody = [NSMutableData data];
|
||||||
|
|
||||||
|
// first parameter an image
|
||||||
|
[postBody appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
|
||||||
|
[postBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"upload\"; filename=\"%@\"\r\n", filename] dataUsingEncoding:NSUTF8StringEncoding]];
|
||||||
|
[postBody appendData:[@"Content-Type: image/png\r\n\r\n" dataUsingEncoding : NSUTF8StringEncoding]];
|
||||||
|
[postBody appendData:imageData];
|
||||||
|
|
||||||
|
// // second parameter information
|
||||||
|
// [postBody appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
|
||||||
|
// [postBody appendData:[@"Content-Disposition: form-data; name=\"some_other_name\"\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
|
||||||
|
// [postBody appendData:[@"some_other_value" dataUsingEncoding:NSUTF8StringEncoding]];
|
||||||
|
// [postBody appendData:[[NSString stringWithFormat:@"\r\n--%@--\r \n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
|
||||||
|
|
||||||
|
[req setHTTPBody:postBody];
|
||||||
|
|
||||||
|
NSURLResponse* response;
|
||||||
|
NSError* error;
|
||||||
|
[NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&error];
|
||||||
|
|
||||||
|
// NSData* result = [NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&error];
|
||||||
|
// NSString * resultStr = [[[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding] autorelease];
|
||||||
|
|
||||||
|
self.hasPendingOperation = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
- (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) {
|
||||||
|
[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) {
|
||||||
|
[self.locationManager stopUpdatingLocation];
|
||||||
|
self.locationManager = nil;
|
||||||
|
|
||||||
|
[self imagePickerControllerReturnImageResult];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)imagePickerControllerReturnImageResult
|
||||||
|
{
|
||||||
|
CDVPluginResult* result = nil;
|
||||||
|
|
||||||
|
if (self.metadata) {
|
||||||
|
CGImageSourceRef sourceImage = CGImageSourceCreateWithData((__bridge_retained 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.pickerController.saveToPhotoAlbum) {
|
||||||
|
UIImageWriteToSavedPhotosAlbum([UIImage imageWithData:[self data]], nil, nil, 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]];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[NSURL fileURLWithPath:filePath] absoluteString]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[self.data base64EncodedString]];
|
||||||
|
}
|
||||||
|
if (result) {
|
||||||
|
[self.commandDelegate sendPluginResult:result callbackId:self.pickerController.callbackId];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
[self.commandDelegate sendPluginResult:result callbackId:self.pickerController.callbackId];
|
||||||
|
}
|
||||||
|
|
||||||
|
self.hasPendingOperation = NO;
|
||||||
|
self.pickerController = nil;
|
||||||
|
self.data = nil;
|
||||||
|
self.metadata = 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;
|
||||||
|
|
||||||
|
@end
|
43
src/ios/CDVExif.h
Normal file
43
src/ios/CDVExif.h
Normal 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
|
62
src/ios/CDVJpegHeaderWriter.h
Normal file
62
src/ios/CDVJpegHeaderWriter.h
Normal 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
|
547
src/ios/CDVJpegHeaderWriter.m
Normal file
547
src/ios/CDVJpegHeaderWriter.m
Normal 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:¤tDataOffset];
|
||||||
|
|
||||||
|
//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:¤tDataOffset];
|
||||||
|
/*
|
||||||
|
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,
|
||||||
|
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
|
||||||
|
int entrycount = [ifdblock count];
|
||||||
|
if (ifd0flag) {
|
||||||
|
// 18 accounts for 8769's width + offset to next ifd, 8 accounts for start of header
|
||||||
|
NSNumber * offset = [NSNumber numberWithInt:[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 numberWithInt: [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", 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 {
|
||||||
|
int 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
|
Loading…
x
Reference in New Issue
Block a user