mirror of
https://github.com/apache/cordova-plugin-camera.git
synced 2025-03-15 13:31:03 +08:00
548 lines
26 KiB
Objective-C
548 lines
26 KiB
Objective-C
/*
|
|
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,
|
|
(unsigned int)(16 + ([exifIFD length]/2) + ([subExifIFD length]/2)) /*16+[exifIFD length]/2*/,
|
|
exifmarker,
|
|
tiffheader,
|
|
ifd0offset,
|
|
exifIFD,
|
|
subExifIFD];
|
|
|
|
return app1;
|
|
}
|
|
|
|
// returns hex string representing a valid exif information file directory constructed from the datadict and formatdict
|
|
- (NSString*) createExifIFDFromDict : (NSDictionary*) datadict
|
|
withFormatDict : (NSDictionary*) formatdict
|
|
isIFD0 : (BOOL) ifd0flag
|
|
currentDataOffset : (int*) dataoffset {
|
|
NSArray * datakeys = [datadict allKeys]; // all known data keys
|
|
NSArray * knownkeys = [formatdict allKeys]; // only keys in knowkeys are considered for entry in this IFD
|
|
NSMutableArray * ifdblock = [[NSMutableArray alloc] initWithCapacity: [datadict count]]; // all ifd entries
|
|
NSMutableArray * ifddatablock = [[NSMutableArray alloc] initWithCapacity: [datadict count]]; // data block entries
|
|
// ifd0flag = NO; // ifd0 requires a special flag and has offset to next ifd appended to end
|
|
|
|
// iterate through known provided data keys
|
|
for (int i = 0; i < [datakeys count]; i++) {
|
|
NSString * key = [datakeys objectAtIndex:i];
|
|
// don't muck about with unknown keys
|
|
if ([knownkeys indexOfObject: key] != NSNotFound) {
|
|
// create new IFD entry
|
|
NSString * entry = [self createIFDElement: key
|
|
withFormat: [formatdict objectForKey:key]
|
|
withElementData: [datadict objectForKey:key]];
|
|
// create the IFD entry's data block
|
|
NSString * data = [self createIFDElementDataWithFormat: [formatdict objectForKey:key]
|
|
withData: [datadict objectForKey:key]];
|
|
if (entry) {
|
|
[ifdblock addObject:entry];
|
|
if(!data) {
|
|
[ifdblock addObject:@""];
|
|
} else {
|
|
[ifddatablock addObject:data];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
NSMutableString * exifstr = [[NSMutableString alloc] initWithCapacity: [ifdblock count] * 24];
|
|
NSMutableString * dbstr = [[NSMutableString alloc] initWithCapacity: 100];
|
|
|
|
int addr=*dataoffset; // current offset/address in datablock
|
|
if (ifd0flag) {
|
|
// calculate offset to datablock based on ifd file entry count
|
|
addr += 14+(12*([ifddatablock count]+1)); // +1 for tag 0x8769, exifsubifd offset
|
|
} else {
|
|
// current offset + numSubIFDs (2-bytes) + 12*numSubIFDs + endMarker (4-bytes)
|
|
addr += 2+(12*[ifddatablock count])+4;
|
|
}
|
|
|
|
for (int i = 0; i < [ifdblock count]; i++) {
|
|
NSString * entry = [ifdblock objectAtIndex:i];
|
|
NSString * data = [ifddatablock objectAtIndex:i];
|
|
|
|
// check if the data fits into 4 bytes
|
|
if( [data length] <= 8) {
|
|
// concatenate the entry and the (4byte) data entry into the final IFD entry and append to exif ifd string
|
|
[exifstr appendFormat : @"%@%@", entry, data];
|
|
} else {
|
|
[exifstr appendFormat : @"%@%08x", entry, addr];
|
|
[dbstr appendFormat: @"%@", data];
|
|
addr+= [data length] / 2;
|
|
/*
|
|
NSLog(@"=====data-length[%i]=======",[data length]);
|
|
NSLog(@"addr-offset[%i]",addr);
|
|
NSLog(@"entry[%@]",entry);
|
|
NSLog(@"data[%@]",data);
|
|
*/
|
|
}
|
|
}
|
|
|
|
// calculate IFD0 terminal offset tags, currently ExifSubIFD
|
|
unsigned int entrycount = (unsigned int)[ifdblock count];
|
|
if (ifd0flag) {
|
|
// 18 accounts for 8769's width + offset to next ifd, 8 accounts for start of header
|
|
NSNumber * offset = [NSNumber numberWithUnsignedInteger:[exifstr length] / 2 + [dbstr length] / 2 + 18+8];
|
|
|
|
[self appendExifOffsetTagTo: exifstr
|
|
withOffset : offset];
|
|
entrycount++;
|
|
}
|
|
*dataoffset = addr;
|
|
return [[NSString alloc] initWithFormat: @"%04x%@%@%@",
|
|
entrycount,
|
|
exifstr,
|
|
@"00000000", // offset to next IFD, 0 since there is none
|
|
dbstr]; // lastly, the datablock
|
|
}
|
|
|
|
// Creates an exif formatted exif information file directory entry
|
|
- (NSString*) createIFDElement: (NSString*) elementName withFormat: (NSArray*) formtemplate withElementData: (NSString*) data {
|
|
//NSArray * fielddata = [formatdict objectForKey: elementName];// format data of desired field
|
|
if (formtemplate) {
|
|
// format string @"%@%@%@%@", tag number, data format, components, value
|
|
NSNumber * dataformat = [formtemplate objectAtIndex:1];
|
|
NSNumber * components = [formtemplate objectAtIndex:2];
|
|
if([components intValue] == 0) {
|
|
components = [NSNumber numberWithUnsignedInteger:[data length] * DataTypeToWidth[[dataformat intValue]-1]];
|
|
}
|
|
|
|
return [[NSString alloc] initWithFormat: @"%@%@%08x",
|
|
[formtemplate objectAtIndex:0], // the field code
|
|
[self formatNumberWithLeadingZeroes: dataformat withPlaces: @4], // the data type code
|
|
[components intValue]]; // number of components
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* appends exif IFD0 tag 8769 "ExifOffset" to the string provided
|
|
* (NSMutableString*) str - string you wish to append the 8769 tag to: APP1 or IFD0 hex data string
|
|
* // TAGINF(@"8769", [NSNumber numberWithInt:EDT_ULONG], @1), @"ExifOffset",
|
|
*/
|
|
- (void) appendExifOffsetTagTo: (NSMutableString*) str withOffset : (NSNumber*) offset {
|
|
NSArray * format = TAGINF(@"8769", [NSNumber numberWithInt:EDT_ULONG], @1);
|
|
|
|
NSString * entry = [self createIFDElement: @"ExifOffset"
|
|
withFormat: format
|
|
withElementData: [offset stringValue]];
|
|
|
|
NSString * data = [self createIFDElementDataWithFormat: format
|
|
withData: [offset stringValue]];
|
|
[str appendFormat:@"%@%@", entry, data];
|
|
}
|
|
|
|
// formats the Information File Directory Data to exif format
|
|
- (NSString*) createIFDElementDataWithFormat: (NSArray*) dataformat withData: (NSString*) data {
|
|
NSMutableString * datastr = nil;
|
|
NSNumber * tmp = nil;
|
|
NSNumber * formatcode = [dataformat objectAtIndex:1];
|
|
NSUInteger formatItemsCount = [dataformat count];
|
|
NSNumber * num = @0;
|
|
NSNumber * denom = @0;
|
|
|
|
switch ([formatcode intValue]) {
|
|
case EDT_UBYTE:
|
|
break;
|
|
case EDT_ASCII_STRING:
|
|
datastr = [[NSMutableString alloc] init];
|
|
for (int i = 0; i < [data length]; i++) {
|
|
[datastr appendFormat:@"%02x",[data characterAtIndex:i]];
|
|
}
|
|
if (formatItemsCount > 3) {
|
|
// We have additional data to append.
|
|
// currently used by Date format to append final 0x00 but can be used by other data types as well in the future
|
|
[datastr appendString:[dataformat objectAtIndex:3]];
|
|
}
|
|
if ([datastr length] < 8) {
|
|
NSString * format = [NSString stringWithFormat:@"%%0%dd", (int)(8 - [datastr length])];
|
|
[datastr appendFormat:format,0];
|
|
}
|
|
return datastr;
|
|
case EDT_USHORT:
|
|
return [[NSString alloc] initWithFormat : @"%@%@",
|
|
[self formattedHexStringFromDecimalNumber: [NSNumber numberWithInt: [data intValue]] withPlaces: @4],
|
|
@"0000"];
|
|
case EDT_ULONG:
|
|
tmp = [NSNumber numberWithUnsignedLong:[data intValue]];
|
|
return [NSString stringWithFormat : @"%@",
|
|
[self formattedHexStringFromDecimalNumber: tmp withPlaces: @8]];
|
|
case EDT_URATIONAL:
|
|
return [self decimalToUnsignedRational: [NSNumber numberWithDouble:[data doubleValue]]
|
|
withResultNumerator: &num
|
|
withResultDenominator: &denom];
|
|
case EDT_SBYTE:
|
|
|
|
break;
|
|
case EDT_UNDEFINED:
|
|
break; // 8 bits
|
|
case EDT_SSHORT:
|
|
break;
|
|
case EDT_SLONG:
|
|
break; // 32bit signed integer (2's complement)
|
|
case EDT_SRATIONAL:
|
|
break; // 2 SLONGS, first long is numerator, second is denominator
|
|
case EDT_SINGLEFLOAT:
|
|
break;
|
|
case EDT_DOUBLEFLOAT:
|
|
break;
|
|
}
|
|
return datastr;
|
|
}
|
|
|
|
//======================================================================================================================
|
|
// Utility Methods
|
|
//======================================================================================================================
|
|
|
|
// creates a formatted little endian hex string from a number and width specifier
|
|
- (NSString*) formattedHexStringFromDecimalNumber: (NSNumber*) numb withPlaces: (NSNumber*) width {
|
|
NSMutableString * str = [[NSMutableString alloc] initWithCapacity:[width intValue]];
|
|
NSString * formatstr = [[NSString alloc] initWithFormat: @"%%%@%dx", @"0", [width intValue]];
|
|
[str appendFormat:formatstr, [numb intValue]];
|
|
return str;
|
|
}
|
|
|
|
// format number as string with leading 0's
|
|
- (NSString*) formatNumberWithLeadingZeroes: (NSNumber *) numb withPlaces: (NSNumber *) places {
|
|
NSNumberFormatter * formatter = [[NSNumberFormatter alloc] init];
|
|
NSString *formatstr = [@"" stringByPaddingToLength:[places unsignedIntegerValue] withString:@"0" startingAtIndex:0];
|
|
[formatter setPositiveFormat:formatstr];
|
|
return [formatter stringFromNumber:numb];
|
|
}
|
|
|
|
// approximate a decimal with a rational by method of continued fraction
|
|
// can be collasped into decimalToUnsignedRational after testing
|
|
- (void) decimalToRational: (NSNumber *) numb
|
|
withResultNumerator: (NSNumber**) numerator
|
|
withResultDenominator: (NSNumber**) denominator {
|
|
NSMutableArray * fractionlist = [[NSMutableArray alloc] initWithCapacity:8];
|
|
|
|
[self continuedFraction: [numb doubleValue]
|
|
withFractionList: fractionlist
|
|
withHorizon: 8];
|
|
|
|
// simplify complex fraction represented by partial fraction list
|
|
[self expandContinuedFraction: fractionlist
|
|
withResultNumerator: numerator
|
|
withResultDenominator: denominator];
|
|
|
|
}
|
|
|
|
// approximate a decimal with an unsigned rational by method of continued fraction
|
|
- (NSString*) decimalToUnsignedRational: (NSNumber *) numb
|
|
withResultNumerator: (NSNumber**) numerator
|
|
withResultDenominator: (NSNumber**) denominator {
|
|
NSMutableArray * fractionlist = [[NSMutableArray alloc] initWithCapacity:8];
|
|
|
|
// generate partial fraction list
|
|
[self continuedFraction: [numb doubleValue]
|
|
withFractionList: fractionlist
|
|
withHorizon: 8];
|
|
|
|
// simplify complex fraction represented by partial fraction list
|
|
[self expandContinuedFraction: fractionlist
|
|
withResultNumerator: numerator
|
|
withResultDenominator: denominator];
|
|
|
|
return [self formatFractionList: fractionlist];
|
|
}
|
|
|
|
// recursive implementation of decimal approximation by continued fraction
|
|
- (void) continuedFraction: (double) val
|
|
withFractionList: (NSMutableArray*) fractionlist
|
|
withHorizon: (int) horizon {
|
|
int whole;
|
|
double remainder;
|
|
// 1. split term
|
|
[self splitDouble: val withIntComponent: &whole withFloatRemainder: &remainder];
|
|
[fractionlist addObject: [NSNumber numberWithInt:whole]];
|
|
|
|
// 2. calculate reciprocal of remainder
|
|
if (!remainder) return; // early exit, exact fraction found, avoids recip/0
|
|
double recip = 1 / remainder;
|
|
|
|
// 3. exit condition
|
|
if ([fractionlist count] > horizon) {
|
|
return;
|
|
}
|
|
|
|
// 4. recurse
|
|
[self continuedFraction:recip withFractionList: fractionlist withHorizon: horizon];
|
|
|
|
}
|
|
|
|
// expand continued fraction list, creating a single level rational approximation
|
|
-(void) expandContinuedFraction: (NSArray*) fractionlist
|
|
withResultNumerator: (NSNumber**) numerator
|
|
withResultDenominator: (NSNumber**) denominator {
|
|
NSUInteger i = 0;
|
|
int den = 0;
|
|
int num = 0;
|
|
if ([fractionlist count] == 1) {
|
|
*numerator = [NSNumber numberWithInt:[[fractionlist objectAtIndex:0] intValue]];
|
|
*denominator = @1;
|
|
return;
|
|
}
|
|
|
|
//begin at the end of the list
|
|
i = [fractionlist count] - 1;
|
|
num = 1;
|
|
den = [[fractionlist objectAtIndex:i] intValue];
|
|
|
|
while (i > 0) {
|
|
int t = [[fractionlist objectAtIndex: i-1] intValue];
|
|
num = t * den + num;
|
|
if (i==1) {
|
|
break;
|
|
} else {
|
|
t = num;
|
|
num = den;
|
|
den = t;
|
|
}
|
|
i--;
|
|
}
|
|
// set result parameters values
|
|
*numerator = [NSNumber numberWithInt: num];
|
|
*denominator = [NSNumber numberWithInt: den];
|
|
}
|
|
|
|
// formats expanded fraction list to string matching exif specification
|
|
- (NSString*) formatFractionList: (NSArray *) fractionlist {
|
|
NSMutableString * str = [[NSMutableString alloc] initWithCapacity:16];
|
|
|
|
if ([fractionlist count] == 1){
|
|
[str appendFormat: @"%08x00000001", [[fractionlist objectAtIndex:0] intValue]];
|
|
}
|
|
return str;
|
|
}
|
|
|
|
// format rational as
|
|
- (NSString*) formatRationalWithNumerator: (NSNumber*) numerator withDenominator: (NSNumber*) denominator asSigned: (Boolean) signedFlag {
|
|
NSMutableString * str = [[NSMutableString alloc] initWithCapacity:16];
|
|
if (signedFlag) {
|
|
long num = [numerator longValue];
|
|
long den = [denominator longValue];
|
|
[str appendFormat: @"%08lx%08lx", num >= 0 ? num : ~ABS(num) + 1, num >= 0 ? den : ~ABS(den) + 1];
|
|
} else {
|
|
[str appendFormat: @"%08lx%08lx", [numerator unsignedLongValue], [denominator unsignedLongValue]];
|
|
}
|
|
return str;
|
|
}
|
|
|
|
// split a floating point number into two integer values representing the left and right side of the decimal
|
|
- (void) splitDouble: (double) val withIntComponent: (int*) rightside withFloatRemainder: (double*) leftside {
|
|
*rightside = val; // convert numb to int representation, which truncates the decimal portion
|
|
*leftside = val - *rightside;
|
|
}
|
|
|
|
|
|
//
|
|
- (NSString*) hexStringFromData : (NSData*) data {
|
|
//overflow detection
|
|
const unsigned char *dataBuffer = [data bytes];
|
|
return [[NSString alloc] initWithFormat: @"%02x%02x",
|
|
(unsigned char)dataBuffer[0],
|
|
(unsigned char)dataBuffer[1]];
|
|
}
|
|
|
|
// convert a hex string to a number
|
|
- (NSNumber*) numericFromHexString : (NSString *) hexstring {
|
|
NSScanner * scan = NULL;
|
|
unsigned int numbuf= 0;
|
|
|
|
scan = [NSScanner scannerWithString:hexstring];
|
|
[scan scanHexInt:&numbuf];
|
|
return [NSNumber numberWithInt:numbuf];
|
|
}
|
|
|
|
@end
|