arnaucube
arnaucube/openEventsPlatformAppPublic Self-hosted repository
openEventsPlatformApp/plugins/cordova-plugin-camera/src/ios/CDVJpegHeaderWriter.m
plugins/cordova-plugin-camera/src/ios/CDVJpegHeaderWriter.mRaw
  1/*
  2 Licensed to the Apache Software Foundation (ASF) under one
  3 or more contributor license agreements.  See the NOTICE file
  4 distributed with this work for additional information
  5 regarding copyright ownership.  The ASF licenses this file
  6 to you under the Apache License, Version 2.0 (the
  7 "License"); you may not use this file except in compliance
  8 with the License.  You may obtain a copy of the License at
  9
 10 http://www.apache.org/licenses/LICENSE-2.0
 11
 12 Unless required by applicable law or agreed to in writing,
 13 software distributed under the License is distributed on an
 14 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15 KIND, either express or implied.  See the License for the
 16 specific language governing permissions and limitations
 17 under the License.
 18 */
 19
 20#import "CDVJpegHeaderWriter.h"
 21#include "CDVExif.h"
 22
 23/* macros for tag info shorthand:
 24   tagno        : tag number
 25   typecode     : data type
 26   components   : number of components
 27   appendString (TAGINF_W_APPEND only) : string to append to data
 28      Exif date data format include an extra 0x00 to the end of the data
 29 */
 30#define TAGINF(tagno, typecode, components) [NSArray arrayWithObjects: tagno, typecode, components, nil]
 31#define TAGINF_W_APPEND(tagno, typecode, components, appendString) [NSArray arrayWithObjects: tagno, typecode, components, appendString, nil]
 32
 33const uint mJpegId = 0xffd8; // JPEG format marker
 34const uint mExifMarker = 0xffe1; // APP1 jpeg header marker
 35const uint mExif = 0x45786966; // ASCII 'Exif', first characters of valid exif header after size
 36const uint mMotorallaByteAlign = 0x4d4d; // 'MM', motorola byte align, msb first or 'sane'
 37const uint mIntelByteAlgin = 0x4949; // 'II', Intel byte align, lsb first or 'batshit crazy reverso world'
 38const uint mTiffLength = 0x2a; // after byte align bits, next to bits are 0x002a(MM) or 0x2a00(II), tiff version number
 39
 40
 41@implementation CDVJpegHeaderWriter
 42
 43- (id) init {    
 44    self = [super init];
 45    // supported tags for exif IFD
 46    IFD0TagFormatDict = [[NSDictionary alloc] initWithObjectsAndKeys:
 47                  //      TAGINF(@"010e", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"ImageDescription",
 48                        TAGINF_W_APPEND(@"0132", [NSNumber numberWithInt:EDT_ASCII_STRING], @20, @"00"), @"DateTime",
 49                        TAGINF(@"010f", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"Make",
 50                        TAGINF(@"0110", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"Model",
 51                        TAGINF(@"0131", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"Software",
 52                        TAGINF(@"011a", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"XResolution",
 53                        TAGINF(@"011b", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"YResolution",
 54                        // currently supplied outside of Exif data block by UIImagePickerControllerMediaMetadata, this is set manually in CDVCamera.m
 55    /*                    TAGINF(@"0112", [NSNumber numberWithInt:EDT_USHORT], @1), @"Orientation",
 56                       
 57                        // rest of the tags are supported by exif spec, but are not specified by UIImagePickerControllerMediaMedadata
 58                        // should camera hardware supply these values in future versions, or if they can be derived, ImageHeaderWriter will include them gracefully
 59                        TAGINF(@"0128", [NSNumber numberWithInt:EDT_USHORT], @1), @"ResolutionUnit",
 60                        TAGINF(@"013e", [NSNumber numberWithInt:EDT_URATIONAL], @2), @"WhitePoint",
 61                        TAGINF(@"013f", [NSNumber numberWithInt:EDT_URATIONAL], @6), @"PrimaryChromaticities",
 62                        TAGINF(@"0211", [NSNumber numberWithInt:EDT_URATIONAL], @3), @"YCbCrCoefficients",
 63                        TAGINF(@"0213", [NSNumber numberWithInt:EDT_USHORT], @1), @"YCbCrPositioning",
 64                        TAGINF(@"0214", [NSNumber numberWithInt:EDT_URATIONAL], @6), @"ReferenceBlackWhite",
 65                        TAGINF(@"8298", [NSNumber numberWithInt:EDT_URATIONAL], @0), @"Copyright",
 66                         
 67                        // offset to exif subifd, we determine this dynamically based on the size of the main exif IFD
 68                        TAGINF(@"8769", [NSNumber numberWithInt:EDT_ULONG], @1), @"ExifOffset",*/
 69                        nil];
 70
 71
 72    // supported tages for exif subIFD
 73    SubIFDTagFormatDict = [[NSDictionary alloc] initWithObjectsAndKeys:
 74                           //TAGINF(@"9000", [NSNumber numberWithInt:], @), @"ExifVersion",
 75                           //TAGINF(@"9202",[NSNumber numberWithInt:EDT_URATIONAL],@1), @"ApertureValue",
 76                           //TAGINF(@"9203",[NSNumber numberWithInt:EDT_SRATIONAL],@1), @"BrightnessValue",
 77                           TAGINF(@"a001",[NSNumber numberWithInt:EDT_USHORT],@1), @"ColorSpace",
 78                           TAGINF_W_APPEND(@"9004",[NSNumber numberWithInt:EDT_ASCII_STRING],@20,@"00"), @"DateTimeDigitized",
 79                           TAGINF_W_APPEND(@"9003",[NSNumber numberWithInt:EDT_ASCII_STRING],@20,@"00"), @"DateTimeOriginal",
 80                           TAGINF(@"a402", [NSNumber numberWithInt:EDT_USHORT], @1), @"ExposureMode",
 81                           TAGINF(@"8822", [NSNumber numberWithInt:EDT_USHORT], @1), @"ExposureProgram",
 82                           //TAGINF(@"829a", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"ExposureTime",
 83                           //TAGINF(@"829d", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"FNumber",
 84                           TAGINF(@"9209", [NSNumber numberWithInt:EDT_USHORT], @1), @"Flash",
 85                           // FocalLengthIn35mmFilm
 86                           TAGINF(@"a405", [NSNumber numberWithInt:EDT_USHORT], @1), @"FocalLenIn35mmFilm",
 87                           //TAGINF(@"920a", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"FocalLength",
 88                           //TAGINF(@"8827", [NSNumber numberWithInt:EDT_USHORT], @2), @"ISOSpeedRatings",
 89                           TAGINF(@"9207", [NSNumber numberWithInt:EDT_USHORT],@1), @"MeteringMode",
 90                           // specific to compressed data
 91                           TAGINF(@"a002", [NSNumber numberWithInt:EDT_ULONG],@1), @"PixelXDimension",
 92                           TAGINF(@"a003", [NSNumber numberWithInt:EDT_ULONG],@1), @"PixelYDimension",
 93                           // data type undefined, but this is a DSC camera, so value is always 1, treat as ushort
 94                           TAGINF(@"a301", [NSNumber numberWithInt:EDT_USHORT],@1), @"SceneType",
 95                           TAGINF(@"a217",[NSNumber numberWithInt:EDT_USHORT],@1), @"SensingMethod",
 96                           //TAGINF(@"9201", [NSNumber numberWithInt:EDT_SRATIONAL], @1), @"ShutterSpeedValue",
 97                           // specifies location of main subject in scene (x,y,wdith,height) expressed before rotation processing
 98                           //TAGINF(@"9214", [NSNumber numberWithInt:EDT_USHORT], @4), @"SubjectArea",
 99                           TAGINF(@"a403", [NSNumber numberWithInt:EDT_USHORT], @1), @"WhiteBalance",
100                           nil];
101    return self;
102}
103
104- (NSData*) spliceExifBlockIntoJpeg: (NSData*) jpegdata withExifBlock: (NSString*) exifstr {
105    
106    CDVJpegHeaderWriter * exifWriter = [[CDVJpegHeaderWriter alloc] init];
107    
108    NSMutableData * exifdata = [NSMutableData dataWithCapacity: [exifstr length]/2];
109    int idx;
110    for (idx = 0; idx+1 < [exifstr length]; idx+=2) {
111        NSRange range = NSMakeRange(idx, 2);
112        NSString* hexStr = [exifstr substringWithRange:range];
113        NSScanner* scanner = [NSScanner scannerWithString:hexStr];
114        unsigned int intValue;
115        [scanner scanHexInt:&intValue];
116        [exifdata appendBytes:&intValue length:1];
117    }
118    
119    NSMutableData * ddata = [NSMutableData dataWithCapacity: [jpegdata length]];
120    NSMakeRange(0,4);
121    int loc = 0;
122    bool done = false;
123    // read the jpeg data until we encounter the app1==0xFFE1 marker
124    while (loc+1 < [jpegdata length]) {
125        NSData * blag = [jpegdata subdataWithRange: NSMakeRange(loc,2)];
126        if( [[blag description] isEqualToString : @"<ffe1>"]) {
127            // read the APP1 block size bits
128            NSString * the = [exifWriter hexStringFromData:[jpegdata subdataWithRange: NSMakeRange(loc+2,2)]];
129            NSNumber * app1width = [exifWriter numericFromHexString:the];
130            //consume the original app1 block
131            [ddata appendData:exifdata];
132            // advance our loc marker past app1
133            loc += [app1width intValue] + 2;
134            done = true;
135        } else {
136            if(!done) {
137                [ddata appendData:blag];
138                loc += 2;
139            } else {
140                break;
141            }
142        }
143    }
144    // copy the remaining data
145    [ddata appendData:[jpegdata subdataWithRange: NSMakeRange(loc,[jpegdata length]-loc)]];
146    return ddata;
147}
148
149
150
151/**
152 * Create the Exif data block as a hex string
153 *   jpeg uses Application Markers (APP's) as markers for application data
154 *   APP1 is the application marker reserved for exif data
155 *
156 *   (NSDictionary*) datadict - with subdictionaries marked '{TIFF}' and '{EXIF}' as returned by imagePickerController with a valid
157 *                              didFinishPickingMediaWithInfo data dict, under key @"UIImagePickerControllerMediaMetadata"
158 *
159 *   the following constructs a hex string to Exif specifications, and is therefore brittle
160 *   altering the order of arguments to the string constructors, modifying field sizes or formats,
161 *   and any other minor change will likely prevent the exif data from being read
162 */
163- (NSString*) createExifAPP1 : (NSDictionary*) datadict {
164    NSMutableString * app1; // holds finalized product
165    NSString * exifIFD; // exif information file directory
166    NSString * subExifIFD; // subexif information file directory
167    
168    // FFE1 is the hex APP1 marker code, and will allow client apps to read the data
169    NSString * app1marker = @"ffe1";
170    // SSSS size, to be determined
171    // EXIF ascii characters followed by 2bytes of zeros
172    NSString * exifmarker = @"457869660000";
173    // Tiff header: 4d4d is motorolla byte align (big endian), 002a is hex for 42
174    NSString * tiffheader = @"4d4d002a";
175    //first IFD offset from the Tiff header to IFD0. Since we are writing it, we know it's address 0x08
176    NSString * ifd0offset = @"00000008";
177    // current offset to next data area
178    int currentDataOffset = 0;
179    
180    //data labeled as TIFF in UIImagePickerControllerMediaMetaData is part of the EXIF IFD0 portion of APP1
181    exifIFD = [self createExifIFDFromDict: [datadict objectForKey:@"{TIFF}"] withFormatDict: IFD0TagFormatDict isIFD0:YES currentDataOffset:&currentDataOffset];
182
183    //data labeled as EXIF in UIImagePickerControllerMediaMetaData is part of the EXIF Sub IFD portion of APP1
184    subExifIFD = [self createExifIFDFromDict: [datadict objectForKey:@"{Exif}"] withFormatDict: SubIFDTagFormatDict isIFD0:NO currentDataOffset:&currentDataOffset];
185    /*
186    NSLog(@"SUB EXIF IFD %@  WITH SIZE: %d",exifIFD,[exifIFD length]);
187    
188    NSLog(@"SUB EXIF IFD %@  WITH SIZE: %d",subExifIFD,[subExifIFD length]);
189    */
190    // construct the complete app1 data block
191    app1 = [[NSMutableString alloc] initWithFormat: @"%@%04x%@%@%@%@%@",
192            app1marker,
193            (unsigned int)(16 + ([exifIFD length]/2) + ([subExifIFD length]/2)) /*16+[exifIFD length]/2*/,
194            exifmarker,
195            tiffheader,
196            ifd0offset,
197            exifIFD,
198            subExifIFD];
199     
200    return app1;
201}
202
203// returns hex string representing a valid exif information file directory constructed from the datadict and formatdict
204- (NSString*) createExifIFDFromDict : (NSDictionary*) datadict
205                     withFormatDict : (NSDictionary*) formatdict
206                             isIFD0 : (BOOL) ifd0flag
207                  currentDataOffset : (int*) dataoffset {
208    NSArray * datakeys = [datadict allKeys]; // all known data keys
209    NSArray * knownkeys = [formatdict  allKeys]; // only keys in knowkeys are considered for entry in this IFD
210    NSMutableArray * ifdblock = [[NSMutableArray alloc] initWithCapacity: [datadict count]]; // all ifd entries
211    NSMutableArray * ifddatablock = [[NSMutableArray alloc] initWithCapacity: [datadict count]]; // data block entries
212 //   ifd0flag = NO; // ifd0 requires a special flag and has offset to next ifd appended to end
213    
214    // iterate through known provided data keys
215    for (int i = 0; i < [datakeys count]; i++) {
216        NSString * key = [datakeys objectAtIndex:i];
217        // don't muck about with unknown keys
218        if ([knownkeys indexOfObject: key] != NSNotFound) {
219            // create new IFD entry
220            NSString * entry = [self  createIFDElement: key
221                                            withFormat: [formatdict objectForKey:key]
222                                      withElementData: [datadict objectForKey:key]];
223            // create the IFD entry's data block
224            NSString * data = [self createIFDElementDataWithFormat: [formatdict objectForKey:key]
225                                                   withData: [datadict objectForKey:key]];
226            if (entry) {
227                [ifdblock addObject:entry];
228                if(!data) {
229                    [ifdblock addObject:@""];
230                } else {
231                    [ifddatablock addObject:data];
232                }
233            }
234        }
235    }
236    
237    NSMutableString * exifstr = [[NSMutableString alloc] initWithCapacity: [ifdblock count] * 24];
238    NSMutableString * dbstr = [[NSMutableString alloc] initWithCapacity: 100];
239    
240    int addr=*dataoffset; // current offset/address in datablock
241    if (ifd0flag) {
242        // calculate offset to datablock based on ifd file entry count
243        addr += 14+(12*([ifddatablock count]+1)); // +1 for tag 0x8769, exifsubifd offset
244    } else {
245        // current offset + numSubIFDs (2-bytes) + 12*numSubIFDs + endMarker (4-bytes)
246        addr += 2+(12*[ifddatablock count])+4;
247    }
248    
249    for (int i = 0; i < [ifdblock count]; i++) {
250        NSString * entry = [ifdblock objectAtIndex:i];
251        NSString * data = [ifddatablock objectAtIndex:i];
252        
253        // check if the data fits into 4 bytes
254        if( [data length] <= 8) {
255            // concatenate the entry and the (4byte) data entry into the final IFD entry and append to exif ifd string
256            [exifstr appendFormat : @"%@%@", entry, data];
257        } else {
258            [exifstr appendFormat : @"%@%08x", entry, addr];
259            [dbstr appendFormat: @"%@", data];
260            addr+= [data length] / 2;
261            /*
262            NSLog(@"=====data-length[%i]=======",[data length]);
263            NSLog(@"addr-offset[%i]",addr);
264            NSLog(@"entry[%@]",entry);
265            NSLog(@"data[%@]",data);
266             */
267        }
268    }
269    
270    // calculate IFD0 terminal offset tags, currently ExifSubIFD
271    unsigned int entrycount = (unsigned int)[ifdblock count];
272    if (ifd0flag) {
273        // 18 accounts for 8769's width + offset to next ifd, 8 accounts for start of header
274        NSNumber * offset = [NSNumber numberWithUnsignedInteger:[exifstr length] / 2 + [dbstr length] / 2 + 18+8];
275        
276        [self appendExifOffsetTagTo: exifstr
277                        withOffset : offset];
278        entrycount++;
279    }
280    *dataoffset = addr;
281    return [[NSString alloc] initWithFormat: @"%04x%@%@%@",
282            entrycount,
283            exifstr,
284            @"00000000", // offset to next IFD, 0 since there is none
285            dbstr]; // lastly, the datablock
286}
287
288// Creates an exif formatted exif information file directory entry
289- (NSString*) createIFDElement: (NSString*) elementName withFormat: (NSArray*) formtemplate withElementData: (NSString*) data  {
290    //NSArray * fielddata = [formatdict objectForKey: elementName];// format data of desired field
291    if (formtemplate) {
292        // format string @"%@%@%@%@", tag number, data format, components, value
293        NSNumber * dataformat = [formtemplate objectAtIndex:1];
294        NSNumber * components = [formtemplate objectAtIndex:2];
295        if([components intValue] == 0) {
296            components = [NSNumber numberWithUnsignedInteger:[data length] * DataTypeToWidth[[dataformat intValue]-1]];
297        }
298
299        return [[NSString alloc] initWithFormat: @"%@%@%08x",
300                                                [formtemplate objectAtIndex:0], // the field code
301                                                [self formatNumberWithLeadingZeroes: dataformat withPlaces: @4], // the data type code
302                                                [components intValue]]; // number of components
303    }
304    return NULL;
305}
306
307/**
308 * appends exif IFD0 tag 8769 "ExifOffset" to the string provided
309 * (NSMutableString*) str - string you wish to append the 8769 tag to: APP1 or IFD0 hex data string 
310 *  //  TAGINF(@"8769", [NSNumber numberWithInt:EDT_ULONG], @1), @"ExifOffset",
311 */
312- (void) appendExifOffsetTagTo: (NSMutableString*) str withOffset : (NSNumber*) offset {
313    NSArray * format = TAGINF(@"8769", [NSNumber numberWithInt:EDT_ULONG], @1);
314    
315    NSString * entry = [self  createIFDElement: @"ExifOffset"
316                                withFormat: format
317                               withElementData: [offset stringValue]];
318    
319    NSString * data = [self createIFDElementDataWithFormat: format
320                                                  withData: [offset stringValue]];
321    [str appendFormat:@"%@%@", entry, data];
322}
323
324// formats the Information File Directory Data to exif format
325- (NSString*) createIFDElementDataWithFormat: (NSArray*) dataformat withData: (NSString*) data {
326    NSMutableString * datastr = nil;
327    NSNumber * tmp = nil;
328    NSNumber * formatcode = [dataformat objectAtIndex:1];
329    NSUInteger formatItemsCount = [dataformat count];
330    NSNumber * num = @0;
331    NSNumber * denom = @0;
332    
333    switch ([formatcode intValue]) {
334        case EDT_UBYTE:
335            break;
336        case EDT_ASCII_STRING:
337            datastr = [[NSMutableString alloc] init];
338            for (int i = 0; i < [data length]; i++) {
339                [datastr appendFormat:@"%02x",[data characterAtIndex:i]];
340            }
341            if (formatItemsCount > 3) {
342                // We have additional data to append.
343                // currently used by Date format to append final 0x00 but can be used by other data types as well in the future
344                [datastr appendString:[dataformat objectAtIndex:3]];
345            }
346            if ([datastr length] < 8) {
347                NSString * format = [NSString stringWithFormat:@"%%0%dd", (int)(8 - [datastr length])];
348                [datastr appendFormat:format,0];
349            }
350            return datastr;
351        case EDT_USHORT:
352            return [[NSString alloc] initWithFormat : @"%@%@",
353                    [self formattedHexStringFromDecimalNumber: [NSNumber numberWithInt: [data intValue]] withPlaces: @4],
354                    @"0000"];
355        case EDT_ULONG:
356            tmp = [NSNumber numberWithUnsignedLong:[data intValue]];
357            return [NSString stringWithFormat : @"%@",
358                    [self formattedHexStringFromDecimalNumber: tmp withPlaces: @8]];
359        case EDT_URATIONAL:
360            return [self decimalToUnsignedRational: [NSNumber numberWithDouble:[data doubleValue]]
361                               withResultNumerator: &num
362                             withResultDenominator: &denom];
363        case EDT_SBYTE:
364            
365            break;
366        case EDT_UNDEFINED:
367            break;     // 8 bits
368        case EDT_SSHORT:
369            break;
370        case EDT_SLONG:
371            break;          // 32bit signed integer (2's complement)
372        case EDT_SRATIONAL:
373            break;     // 2 SLONGS, first long is numerator, second is denominator
374        case EDT_SINGLEFLOAT:
375            break;
376        case EDT_DOUBLEFLOAT:
377            break;
378    }
379    return datastr;
380}
381
382//======================================================================================================================
383// Utility Methods
384//======================================================================================================================
385
386// creates a formatted little endian hex string from a number and width specifier
387- (NSString*) formattedHexStringFromDecimalNumber: (NSNumber*) numb withPlaces: (NSNumber*) width {
388    NSMutableString * str = [[NSMutableString alloc] initWithCapacity:[width intValue]];
389    NSString * formatstr = [[NSString alloc] initWithFormat: @"%%%@%dx", @"0", [width intValue]];
390    [str appendFormat:formatstr, [numb intValue]];
391    return str;
392}
393
394// format number as string with leading 0's
395- (NSString*) formatNumberWithLeadingZeroes: (NSNumber *) numb withPlaces: (NSNumber *) places { 
396    NSNumberFormatter * formatter = [[NSNumberFormatter alloc] init];
397    NSString *formatstr = [@"" stringByPaddingToLength:[places unsignedIntegerValue] withString:@"0" startingAtIndex:0];
398    [formatter setPositiveFormat:formatstr];
399    return [formatter stringFromNumber:numb];
400}
401
402// approximate a decimal with a rational by method of continued fraction
403// can be collasped into decimalToUnsignedRational after testing
404- (void) decimalToRational: (NSNumber *) numb
405       withResultNumerator: (NSNumber**) numerator
406     withResultDenominator: (NSNumber**) denominator {
407    NSMutableArray * fractionlist = [[NSMutableArray alloc] initWithCapacity:8];
408    
409    [self continuedFraction: [numb doubleValue]
410           withFractionList: fractionlist
411                withHorizon: 8];
412    
413    // simplify complex fraction represented by partial fraction list
414    [self expandContinuedFraction: fractionlist
415              withResultNumerator: numerator
416            withResultDenominator: denominator];
417
418}
419
420// approximate a decimal with an unsigned rational by method of continued fraction
421- (NSString*) decimalToUnsignedRational: (NSNumber *) numb
422                          withResultNumerator: (NSNumber**) numerator
423                        withResultDenominator: (NSNumber**) denominator {
424    NSMutableArray * fractionlist = [[NSMutableArray alloc] initWithCapacity:8];
425    
426    // generate partial fraction list
427    [self continuedFraction: [numb doubleValue]
428           withFractionList: fractionlist
429                withHorizon: 8];
430    
431    // simplify complex fraction represented by partial fraction list
432    [self expandContinuedFraction: fractionlist
433              withResultNumerator: numerator
434            withResultDenominator: denominator];
435    
436    return [self formatFractionList: fractionlist];
437}
438
439// recursive implementation of decimal approximation by continued fraction
440- (void) continuedFraction: (double) val
441          withFractionList: (NSMutableArray*) fractionlist
442               withHorizon: (int) horizon {
443    int whole;
444    double remainder;
445    // 1. split term
446    [self splitDouble: val withIntComponent: &whole withFloatRemainder: &remainder];
447    [fractionlist addObject: [NSNumber numberWithInt:whole]];
448    
449    // 2. calculate reciprocal of remainder
450    if (!remainder) return; // early exit, exact fraction found, avoids recip/0
451    double recip = 1 / remainder;
452
453    // 3. exit condition
454    if ([fractionlist count] > horizon) {
455        return;
456    }
457    
458    // 4. recurse
459    [self continuedFraction:recip withFractionList: fractionlist withHorizon: horizon];
460    
461}
462
463// expand continued fraction list, creating a single level rational approximation
464-(void) expandContinuedFraction: (NSArray*) fractionlist
465                  withResultNumerator: (NSNumber**) numerator
466                withResultDenominator: (NSNumber**) denominator {
467    NSUInteger i = 0;
468    int den = 0;
469    int num = 0;
470    if ([fractionlist count] == 1) {
471        *numerator = [NSNumber numberWithInt:[[fractionlist objectAtIndex:0] intValue]];
472        *denominator = @1;
473        return;
474    }
475    
476    //begin at the end of the list
477    i = [fractionlist count] - 1;
478    num = 1;
479    den = [[fractionlist objectAtIndex:i] intValue];
480    
481    while (i > 0) {
482        int t = [[fractionlist objectAtIndex: i-1] intValue];
483        num = t * den + num;
484        if (i==1) {
485            break;
486        } else {
487            t = num;
488            num = den;
489            den = t;
490        }
491        i--;
492    }
493    // set result parameters values
494    *numerator = [NSNumber numberWithInt: num];
495    *denominator = [NSNumber numberWithInt: den];
496}
497
498// formats expanded fraction list to string matching exif specification
499- (NSString*) formatFractionList: (NSArray *) fractionlist {
500    NSMutableString * str = [[NSMutableString alloc] initWithCapacity:16];
501    
502    if ([fractionlist count] == 1){
503        [str appendFormat: @"%08x00000001", [[fractionlist objectAtIndex:0] intValue]];
504    }
505    return str;
506}
507
508// format rational as
509- (NSString*) formatRationalWithNumerator: (NSNumber*) numerator withDenominator: (NSNumber*) denominator asSigned: (Boolean) signedFlag {
510    NSMutableString * str = [[NSMutableString alloc] initWithCapacity:16];
511    if (signedFlag) {
512        long num = [numerator longValue];
513        long den = [denominator longValue];
514        [str appendFormat: @"%08lx%08lx", num >= 0 ? num : ~ABS(num) + 1, num >= 0 ? den : ~ABS(den) + 1];
515    } else {
516        [str appendFormat: @"%08lx%08lx", [numerator unsignedLongValue], [denominator unsignedLongValue]];
517    }
518    return str;
519}
520
521// split a floating point number into two integer values representing the left and right side of the decimal
522- (void) splitDouble: (double) val withIntComponent: (int*) rightside withFloatRemainder: (double*) leftside {
523    *rightside = val; // convert numb to int representation, which truncates the decimal portion
524    *leftside = val - *rightside;
525}
526
527
528//
529- (NSString*) hexStringFromData : (NSData*) data {
530    //overflow detection
531    const unsigned char *dataBuffer = [data bytes];
532    return [[NSString alloc] initWithFormat: @"%02x%02x",
533            (unsigned char)dataBuffer[0],
534            (unsigned char)dataBuffer[1]];
535}
536
537// convert a hex string to a number
538- (NSNumber*) numericFromHexString : (NSString *) hexstring {
539    NSScanner * scan = NULL;
540    unsigned int numbuf= 0;
541    
542    scan = [NSScanner scannerWithString:hexstring];
543    [scan scanHexInt:&numbuf];
544    return [NSNumber numberWithInt:numbuf];
545}
546
547@end