首先说下接触Exif的背景,公司有拍照取证的功能,比如,发生了车祸,需要拍下现场照片取证,除了添加拍摄地点,经纬度,app账号,还有防伪功能。也就是防止别人篡改照片,修改相应的信息。故提出在照片的exif信息中添加防伪的唯一信息。
什么是Exif
可交换图像文件格式(英语:Exchangeable image file format,官方简称Exif),是专门为数码相机的照片设定的,可以记录数码照片的属性信息和拍摄数据。
Exif最初由日本电子工业发展协会在1996年制定,版本为1.0。1998年,升级到2.1,增加了对音频文件的支持。2002年3月,发表了2.2版。
Exif可以附加于JPEG、TIFF、RIFF等文件之中,为其增加有关数码相机拍摄信息的内容和索引图或图像处理软件的版本信息。
Windows 7操作系统具备对Exif的原生支持,通过鼠标右键点击图片打开菜单,点击属性并切换到详细信息标签下即可直接查看Exif信息。
Exif信息是可以被任意编辑的,因此只有参考的功能。Exif信息以0xFFE1作为开头标记,后两个字节表示Exif信息的长度。所以Exif信息最大为64 kb,而内部采用TIFF格式。
一、 imageIO的初步了解
ImageIO框架提供了读取与写入图片数据的基本方法,使用它可以直接获取到图片文件的内容数据,ImageIO框架中包含6个头文件,其中完成主要功能的是CGImageSource和CGImageDestination两个头文件中定义的方法:
- CGImageSource: 负责图片数据的读取。
- CGImageDestination: 负责图片数据的写入。
- CGImageMetadata: 图片文件数据类。
- CGImageProperties: 框架中用到的字符串常量和宏。
- ImageIOBase: 预处理逻辑。
二、 项目应用
图片信息操作都是针对二进制数据Data来操作的,注(在图片转data和data转Image的时候,苹果会将部分exif信息抹除,所以如果需要保存数据到本地,最好通过data数据保存,上传图片数据到服务器,也建议通过data上传)。
废话不多说先上代码:
- 通过图片data数据获取图片图片资源引用。
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
- 再通过图片资源获取图片信息
NSDictionary *imageInfo = (__bridge NSDictionary*)CGImageSourceCopyPropertiesAtIndex(source, 0, NULL);
imageInfo其实都是key是定义好的字典对象。
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyTIFFDictionary IMAGEIO_AVAILABLE_STARTING(10.4, 4.0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyGIFDictionary IMAGEIO_AVAILABLE_STARTING(10.4, 4.0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyJFIFDictionary IMAGEIO_AVAILABLE_STARTING(10.4, 4.0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyExifDictionary IMAGEIO_AVAILABLE_STARTING(10.4, 4.0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyPNGDictionary IMAGEIO_AVAILABLE_STARTING(10.4, 4.0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyIPTCDictionary IMAGEIO_AVAILABLE_STARTING(10.4, 4.0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyGPSDictionary IMAGEIO_AVAILABLE_STARTING(10.4, 4.0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyRawDictionary IMAGEIO_AVAILABLE_STARTING(10.4, 4.0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyCIFFDictionary IMAGEIO_AVAILABLE_STARTING(10.4, 4.0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyMakerCanonDictionary IMAGEIO_AVAILABLE_STARTING(10.5, 4.0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyMakerNikonDictionary IMAGEIO_AVAILABLE_STARTING(10.5, 4.0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyMakerMinoltaDictionary IMAGEIO_AVAILABLE_STARTING(10.5, 4.0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyMakerFujiDictionary IMAGEIO_AVAILABLE_STARTING(10.5, 4.0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyMakerOlympusDictionary IMAGEIO_AVAILABLE_STARTING(10.5, 4.0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyMakerPentaxDictionary IMAGEIO_AVAILABLE_STARTING(10.5, 4.0);
IMAGEIO_EXTERN const CFStringRef kCGImageProperty8BIMDictionary IMAGEIO_AVAILABLE_STARTING(10.4, 4.0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyDNGDictionary IMAGEIO_AVAILABLE_STARTING(10.5, 4.0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyExifAuxDictionary IMAGEIO_AVAILABLE_STARTING(10.5, 4.0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyOpenEXRDictionary IMAGEIO_AVAILABLE_STARTING(10.9, 11.3);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyMakerAppleDictionary IMAGEIO_AVAILABLE_STARTING(10.10, 7.0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyFileContentsDictionary IMAGEIO_AVAILABLE_STARTING(10.13, 11.0);
- 接下来重头戏来了,也就是对图片信息进行编辑。
首先对copy一份新的json对象,然后再对拷贝的新对象进行修改。
NSMutableDictionary *metaDataDic = [imageInfo mutableCopy];
NSMutableDictionary *exifDic =[[metaDataDic objectForKey:(NSString*)kCGImagePropertyExifDictionary]mutableCopy];
添加自定义的对象,但key是固定的,使用的是kCGImagePropertyExifUserComment。
自定义对象里用的是进行加密后的手机号码。记住,exif信息对数据的结构有严格要求,必须严格遵守,不然,可能无法添加成功。这里我只找到了UserComment字段来存放自定义的字符串信息。
/****** begin 添加手机号码 ******/
NSString *dateString = [self.dateFormatter stringFromDate:[NSDate date]];
[exifDic setValue:dateString forKey:(NSString *)kCGImagePropertyExifDateTimeOriginal];
NSString *phone = [NSString stringWithFormat:@"xxxsdk_%@",[PMPluginLoader sharedInstance].pluginMobilePhone];
NSString *decryptKey = [PMPluginUtils pluginRunEnv] == PluginEnv_stg ? kDescryptKey_stg: kDescryptKey_prd;
phone = [PMAESUtils threeEncrypt:phone withKey:decryptKey];
[exifDic setObject:phone forKey:(NSString *)kCGImagePropertyExifUserComment];
/****** end 添加手机号码 ******/
/****** begin 添加定位信息 ******/
NSMutableDictionary *GPSDic =[[metaDataDic objectForKey:(NSString*)kCGImagePropertyGPSDictionary]mutableCopy];
if (!GPSDic) {
GPSDic = [NSMutableDictionary dictionaryWithCapacity:2];
}
CGFloat latitudeValue = self.locaiton.coordinate.latitude;
CGFloat longitudeValue = self.locaiton.coordinate.longitude;
[GPSDic setObject: [NSNumber numberWithFloat:latitudeValue] forKey:(NSString*)kCGImagePropertyGPSLatitude];
[GPSDic setObject:(latitudeValue>0?@"N":@"S") forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
[GPSDic setObject: [NSNumber numberWithFloat:longitudeValue] forKey:(NSString*)kCGImagePropertyGPSLongitude];
[GPSDic setObject:(longitudeValue>0?@"E":@"W") forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
//注意: 设置的value值必须是数值对象。且必须同步设置经纬度,以及东西经、南北纬。少一个都会设置失败。
/****** end 添加定位信息 ******/
/****** begin 添加时间DateTime ******/
NSMutableDictionary *TIFFDic =[[metaDataDic objectForKey:(NSString*)kCGImagePropertyTIFFDictionary]mutableCopy];
if (!TIFFDic) {
TIFFDic = [NSMutableDictionary dictionary];
}
NSString *dateTimeString = [[TIFFDic objectForKey:(NSString*)kCGImagePropertyTIFFDateTime]mutableCopy];
if (!dateTimeString) {
[TIFFDic setObject:dateString forKey:(NSString *)kCGImagePropertyTIFFDateTime];
}
/****** end 添加时间DateTime ******/
//将修改后的信息重新设置到对应的json对象里。
[metaDataDic setValue:GPSDic forKey:(NSString *)kCGImagePropertyGPSDictionary];
[metaDataDic setValue:exifDic forKey:(NSString *)kCGImagePropertyExifDictionary];
[metaDataDic setValue:TIFFDic forKey:(NSString *)kCGImagePropertyTIFFDictionary];
- 保存添加exif信息后的图片data。
CFStringRef UTI = CGImageSourceGetType(source);
//创建空的data对象
NSMutableData *newImageData = [NSMutableData data];
//根据容器data,UTl对象创建图片写入句柄对象。
CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)newImageData, UTI, 1,NULL);
//将图片信息写入图片句柄对象。
CGImageDestinationAddImageFromSource(destination, source, 0, (__bridge CFDictionaryRef)metaDataDic);
BOOL success = CGImageDestinationFinalize(destination);
CFRelease(UTI);
CFRelease(destination);
CFRelease(source);
if (!success) {
NSLog(@"添加exif信息失败");
}
return newImageData; //添加exif信息后的图片data对象。
最后,在项目里面因为也用到了添加水印的功能,再次也将相应代码贴出,供有需要的同学参考。
+ (UIImage *)addImageWatermark:(UIImage *)image text:(NSString *)text{
CGFloat imageWidth = image.size.width;
CGFloat imageHeight = image.size.height;
if (imageWidth/imageHeight > kScreenWidth/kScreenHeight) {
CGFloat finalImageWidth = (imageHeight*kScreenWidth/kScreenHeight);
image = [self tp_imagecutWithOriginalImage:image withCutRect:CGRectMake((imageWidth - finalImageWidth)*0.5, 0, finalImageWidth, imageHeight)];
}else{
CGFloat finalImageHeight = (imageWidth*kScreenHeight/kScreenWidth);
image = [self tp_imagecutWithOriginalImage:image withCutRect:CGRectMake(0, (imageHeight - finalImageHeight)*0.5, imageWidth, finalImageHeight)];
}
image = [UIImage getWaterMarkImage:image andTitle:text andMarkFont:[UIFont boldSystemFontOfSize:20] andMarkColor:[UIColor colorWithRed:192/255.0 green:192/255.0 blue:192/255.0 alpha:1] markMutilple:NO];
return image;
}
+ (UIImage *)tp_imagecutWithOriginalImage:(UIImage *)originalImage withCutRect:(CGRect)rect {
CGImageRef subImageRef = CGImageCreateWithImageInRect(originalImage.CGImage, rect);
CGRect smallRect = CGRectMake(0, 0, CGImageGetWidth(subImageRef), CGImageGetHeight(subImageRef));
// 开启图形上下文
UIGraphicsBeginImageContext(smallRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, smallRect, subImageRef);
UIImage * image = [UIImage imageWithCGImage:subImageRef];
// 关闭图形上下文
UIGraphicsEndImageContext();
CGImageRelease(subImageRef);
return image;
}
结语: 此篇文章的目的,主要是为了记录自己使用过的知识点,一来加深印象,二来也是为自己做下笔记。所以,文章大部分都以代码为主,读起来很是生涉,请谅解。