mirror of
https://github.com/apache/cordova-plugin-camera.git
synced 2025-02-21 04:12:51 +08:00
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…
Reference in New Issue
Block a user