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:¤tDataOffset];
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:¤tDataOffset];
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