| /* |
| 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" |
| |
| // tag info shorthand, tagno: tag number, typecode: data type:, components: number of components |
| #define TAGINF(tagno, typecode, components) [NSArray arrayWithObjects: tagno, typecode, components, 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(@"0132", [NSNumber numberWithInt:EDT_ASCII_STRING], @20), @"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(@"9004",[NSNumber numberWithInt:EDT_ASCII_STRING],@20), @"DateTimeDigitized", |
| TAGINF(@"9003",[NSNumber numberWithInt:EDT_ASCII_STRING],@20), @"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; |
| } |
| |
| /** |
| * 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"; |
| |
| //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]; |
| |
| //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]; |
| |
| // 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 { |
| 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 |
| withFormatDict: formatdict |
| 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=0; // 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 { |
| // same calculation as above, but no exifsubifd offset |
| addr = 14+12*[ifddatablock count]; |
| } |
| |
| 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; |
| } |
| } |
| |
| // calculate IFD0 terminal offset tags, currently ExifSubIFD |
| int entrycount = [ifdblock count]; |
| if (ifd0flag) { |
| NSNumber * offset = [NSNumber numberWithInt:[exifstr length] / 2 + [dbstr length] / 2 ]; |
| |
| [self appendExifOffsetTagTo: exifstr |
| withOffset : offset]; |
| entrycount++; |
| } |
| 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 withFormatDict : (NSDictionary*) formatdict withElementData : (NSString*) data { |
| NSArray * fielddata = [formatdict objectForKey: elementName];// format data of desired field |
| if (fielddata) { |
| // format string @"%@%@%@%@", tag number, data format, components, value |
| NSNumber * dataformat = [fielddata objectAtIndex:1]; |
| NSNumber * components = [fielddata objectAtIndex:2]; |
| if([components intValue] == 0) { |
| components = [NSNumber numberWithInt: [data length] * DataTypeToWidth[[dataformat intValue]-1]]; |
| } |
| |
| return [[NSString alloc] initWithFormat: @"%@%@%08x", |
| [fielddata 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" |
| withFormatDict: IFD0TagFormatDict |
| 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]; |
| 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 ([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], |
| @"00000000"]; |
| 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 |