最近有一個需求,要求用手機拍個照片,並切需要拍攝時間,拍攝gps,拍攝具體街道信息。
首先要感謝PhotoGPSdemo的作者,你可以到這里下載demo http://www.cocoachina.com/bbs/read.php?tid=130501。
以前,總認為jpg就是包含了像素信息的2進制文件,其實,jpg中還可以包含許多起它的信息,只不過我們平常用查看jpg屬性時,系統沒有給我們把信息全部顯示出來而已!
在iOS中,提供了一組函數來操作jpg的這些額外的信息,你需要#import <ImageIO/ImageIO.h>才能使用他們。
首先,需要注意的是,UIImage對象中是沒有這些信息的,它僅僅包含圖像本身,jpg才包含這些信息,請不要弄混。
先看看怎么從jpg圖片中取得各種額外信息,代碼如下
- (IBAction)test { NSString * home = NSHomeDirectory(); NSData *data = [NSData dataWithContentsOfFile:[NSString stringWithFormat:@"%@/Documents/xx.jpg",home]]; CGImageSourceRef imgSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); CFDictionaryRef metaDataDicRef = CGImageSourceCopyPropertiesAtIndex(imgSource,0,nil); NSLog(@"cfdictionary %@",(__bridge NSDictionary *)metaDataDicRef); NSDictionary *dic = (__bridge NSDictionary *)metaDataDicRef; NSDictionary *exifDic = [dic objectForKey:(NSString *)kCGImagePropertyExifDictionary]; NSString *location = [exifDic objectForKey:(NSString *)kCGImagePropertyExifCameraOwnerName]; NSLog(@"location is %@",location); NSString *DateTimeOriginal = [exifDic objectForKey:(NSString *)kCGImagePropertyExifDateTimeOriginal]; NSLog(@"time is %@",DateTimeOriginal); CFRelease(metaDataDicRef); CFRelease(imgSource); }
首先,需要用到CGImageSourceRef類,不能用UIImage類型(需要驗證。。。),這種CGImageSourceRef類型可以通過函數取出jpg的額外數據,這些數據叫做metaData,翻譯過來就是元數據。得到的metaData是以字典形式返回的,其中的key是固定的,具體聲明見下方
CFStringRef kCGImagePropertyTIFFDictionary;
CFStringRef kCGImagePropertyGIFDictionary;
CFStringRef kCGImagePropertyJFIFDictionary;
CFStringRef kCGImagePropertyExifDictionary;
CFStringRef kCGImagePropertyPNGDictionary;
CFStringRef kCGImagePropertyIPTCDictionary;
CFStringRef kCGImagePropertyGPSDictionary;
CFStringRef kCGImagePropertyRawDictionary;
CFStringRef kCGImagePropertyCIFFDictionary;
CFStringRef kCGImageProperty8BIMDictionary;
CFStringRef kCGImagePropertyDNGDictionary;
CFStringRef kCGImagePropertyExifAuxDictionary;
這些key中,有許多key對應的對象還是一個字典,比如kCGImagePropertyExifDictionary所對應的對象包含一下key值
const CFStringRef kCGImagePropertyExifExposureTime; const CFStringRef kCGImagePropertyExifFNumber; const CFStringRef kCGImagePropertyExifExposureProgram; const CFStringRef kCGImagePropertyExifSpectralSensitivity; const CFStringRef kCGImagePropertyExifISOSpeedRatings; const CFStringRef kCGImagePropertyExifOECF; const CFStringRef kCGImagePropertyExifVersion; const CFStringRef kCGImagePropertyExifDateTimeOriginal; const CFStringRef kCGImagePropertyExifDateTimeDigitized; const CFStringRef kCGImagePropertyExifComponentsConfiguration; const CFStringRef kCGImagePropertyExifCompressedBitsPerPixel; const CFStringRef kCGImagePropertyExifShutterSpeedValue; const CFStringRef kCGImagePropertyExifApertureValue; const CFStringRef kCGImagePropertyExifBrightnessValue; const CFStringRef kCGImagePropertyExifExposureBiasValue; const CFStringRef kCGImagePropertyExifMaxApertureValue; const CFStringRef kCGImagePropertyExifSubjectDistance; const CFStringRef kCGImagePropertyExifMeteringMode; const CFStringRef kCGImagePropertyExifLightSource; const CFStringRef kCGImagePropertyExifFlash; const CFStringRef kCGImagePropertyExifFocalLength; const CFStringRef kCGImagePropertyExifSubjectArea; const CFStringRef kCGImagePropertyExifMakerNote; const CFStringRef kCGImagePropertyExifUserComment; const CFStringRef kCGImagePropertyExifSubsecTime; const CFStringRef kCGImagePropertyExifSubsecTimeOrginal; const CFStringRef kCGImagePropertyExifSubsecTimeDigitized; const CFStringRef kCGImagePropertyExifFlashPixVersion; const CFStringRef kCGImagePropertyExifColorSpace; const CFStringRef kCGImagePropertyExifPixelXDimension; const CFStringRef kCGImagePropertyExifPixelYDimension; const CFStringRef kCGImagePropertyExifRelatedSoundFile; const CFStringRef kCGImagePropertyExifFlashEnergy; const CFStringRef kCGImagePropertyExifSpatialFrequencyResponse; const CFStringRef kCGImagePropertyExifFocalPlaneXResolution; const CFStringRef kCGImagePropertyExifFocalPlaneYResolution; const CFStringRef kCGImagePropertyExifFocalPlaneResolutionUnit; const CFStringRef kCGImagePropertyExifSubjectLocation; const CFStringRef kCGImagePropertyExifExposureIndex; const CFStringRef kCGImagePropertyExifSensingMethod; const CFStringRef kCGImagePropertyExifFileSource; const CFStringRef kCGImagePropertyExifSceneType; const CFStringRef kCGImagePropertyExifCFAPattern; const CFStringRef kCGImagePropertyExifCustomRendered; const CFStringRef kCGImagePropertyExifExposureMode; const CFStringRef kCGImagePropertyExifWhiteBalance; const CFStringRef kCGImagePropertyExifDigitalZoomRatio; const CFStringRef kCGImagePropertyExifFocalLenIn35mmFilm; const CFStringRef kCGImagePropertyExifSceneCaptureType; const CFStringRef kCGImagePropertyExifGainControl; const CFStringRef kCGImagePropertyExifContrast; const CFStringRef kCGImagePropertyExifSaturation; const CFStringRef kCGImagePropertyExifSharpness; const CFStringRef kCGImagePropertyExifDeviceSettingDescription; const CFStringRef kCGImagePropertyExifSubjectDistRange; const CFStringRef kCGImagePropertyExifImageUniqueID; const CFStringRef kCGImagePropertyExifGamma; const CFStringRef kCGImagePropertyExifCameraOwnerName; const CFStringRef kCGImagePropertyExifBodySerialNumber; const CFStringRef kCGImagePropertyExifLensSpecification; const CFStringRef kCGImagePropertyExifLensMake; const CFStringRef kCGImagePropertyExifLensModel; const CFStringRef kCGImagePropertyExifLensSerialNumber;
這里的每一個key都有具體的含義,代表了jpg的一些信息。這些key和key的值時有嚴格的格式限定的,你用了自己定義的key或者不符合規則的值,是不能正確地寫入jpg文件的(待會要提到如何寫入metaData信息)。
如何用UIImagePickerController拍攝照片后加入信息呢?用這個控件拍照后,默認生成的metaData信息較少,只有拍攝時間等幾個信息,沒有gps信息,更沒有街道信息,我們需要手動加入。
#pragma mark - UIImagePickerControllerDelegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
_mediaInfo =[NSMutableDictionarydictionaryWithDictionary:info];
if (!_locationManager) {
_locationManager = [[CLLocationManager alloc]init];
[_locationManagersetDelegate:self];
[_locationManagersetDistanceFilter:kCLDistanceFilterNone];
[_locationManagersetDesiredAccuracy:kCLLocationAccuracyBest];
}
[_locationManagerstartUpdatingLocation];
[picker dismissViewControllerAnimated:YEScompletion:^
{
}];
}
#pragma mark CLLocationManagerDelegate
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{
[manager stopUpdatingLocation];
UIImage *originalImage= (UIImage *) [_mediaInfoobjectForKey:UIImagePickerControllerOriginalImage];
NSData *imageNSData = UIImageJPEGRepresentation(originalImage,1);
CGImageSourceRef imgSource = CGImageSourceCreateWithData((__bridge CFDataRef)imageNSData, NULL);
//this is the type of image (e.g., public.jpeg)
CFStringRef UTI = CGImageSourceGetType(imgSource);
//this will be the data CGImageDestinationRef will write into
NSMutableData *newImageData = [NSMutableDatadata];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)newImageData, UTI, 1, NULL);
if(!destination)
{
NSLog(@"***Could not create image destination ***");
return;
}
//get original metadata
NSDictionary *dict = [_mediaInfoobjectForKey:UIImagePickerControllerMediaMetadata];
NSMutableDictionary *metadata = [NSMutableDictionarydictionaryWithDictionary:dict];
//set gps info into metadata,we need a dictionary
NSDictionary * gpsDict=[newLocation GPSDictionary];
if (metadata && gpsDict) {
[metadata setValue:gpsDict forKey:(NSString*)kCGImagePropertyGPSDictionary];
}
//get location detail by CLGeocoder, this needs wifi
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:newLocation completionHandler:^(NSArray *placemarks, NSError *error){
if(error != nil)
{
NSLog(@"CLGeocoder error :%@ ",error);
}
else//if we can get place info ,we set it into meteData dic with kCGImagePropertyExifCameraOwnerName key
{
if(placemarks.count > 0)
{
CLPlacemark *placemark = [placemarks objectAtIndex:0];
NSMutableDictionary *eifDic = (NSMutableDictionary *)[metadata objectForKey:(NSString *)kCGImagePropertyExifDictionary];
[eifDic setObject:placemark.nameforKey:(NSString *)kCGImagePropertyExifCameraOwnerName];
}
}
//add the image contained in the image source to the destination, overidding the old metadata with our modified metadata
CGImageDestinationAddImageFromSource(destination, imgSource, 0, (__bridge CFDictionaryRef) metadata);
BOOL success = NO;
success = CGImageDestinationFinalize(destination);
if(!success) {
NSLog(@"***Could not create data from image destination ***");
return ;
}
CFRelease(imgSource);
CFRelease(destination);
[self sendData:newImageData];
[self writeTest:newImageData];
}];
}
@implementation CLLocation (GPSDictionary)
-(NSDictionary*)GPSDictionary{
NSDateFormatter *formatter = [[NSDateFormatteralloc] init];
[formatter setDateFormat:@"hh:mm:ss.SS"];
CLLocation *location=self;
NSDictionary *gpsDict = [NSDictionarydictionaryWithObjectsAndKeys:
[NSNumbernumberWithFloat:fabs(location.coordinate.latitude)], kCGImagePropertyGPSLatitude,
((location.coordinate.latitude >= 0) ? @"N" : @"S"), kCGImagePropertyGPSLatitudeRef,
[NSNumbernumberWithFloat:fabs(location.coordinate.longitude)], kCGImagePropertyGPSLongitude,
((location.coordinate.longitude >= 0) ? @"E" : @"W"), kCGImagePropertyGPSLongitudeRef,
[formatter stringFromDate:[location timestamp]], kCGImagePropertyGPSTimeStamp,
nil];
NSLog(@"gpsDict i %@",gpsDict);
return gpsDict;
}
@end
上面代碼需要手動改改,它的最終目的就是用 CGImageDestinationAddImageFromSource(destination, imgSource, 0, (__bridge CFDictionaryRef) metadata);函數用新的metaData信息生成新的jpg數據。
其中gps數據用了系統自定義的key值寫入,街道信息沒有找到合適的key,就冒用了kCGImagePropertyExifCameraOwnerName這個key。