iOS-Exif信息的读取和写入

首先说下接触Exif的背景,公司有拍照取证的功能,比如,发生了车祸,需要拍下现场照片取证,除了添加拍摄地点,经纬度,app账号,还有防伪功能。也就是防止别人篡改照片,修改相应的信息。故提出在照片的exif信息中添加防伪的唯一信息。

什么是Exif

可交换图像文件格式(英语:Exchangeable image file format,官方简称Exif),是专门为数码相机的照片设定的,可以记录数码照片的属性信息和拍摄数据。

Exif最初由日本电子工业发展协会在1996年制定,版本为1.0。1998年,升级到2.1,增加了对音频文件的支持。2002年3月,发表了2.2版。

Exif可以附加于JPEGTIFFRIFF等文件之中,为其增加有关数码相机拍摄信息的内容和索引图或图像处理软件的版本信息。

Windows 7操作系统具备对Exif的原生支持,通过鼠标右键点击图片打开菜单,点击属性并切换到详细信息标签下即可直接查看Exif信息。

Exif信息是可以被任意编辑的,因此只有参考的功能。Exif信息以0xFFE1作为开头标记,后两个字节表示Exif信息的长度。所以Exif信息最大为64 kb,而内部采用TIFF格式。

一、 imageIO的初步了解

ImageIO框架提供了读取与写入图片数据的基本方法,使用它可以直接获取到图片文件的内容数据,ImageIO框架中包含6个头文件,其中完成主要功能的是CGImageSource和CGImageDestination两个头文件中定义的方法:
  1. CGImageSource: 负责图片数据的读取。
  2. CGImageDestination: 负责图片数据的写入。
  3. CGImageMetadata: 图片文件数据类。
  4. CGImageProperties: 框架中用到的字符串常量和宏。
  5. ImageIOBase: 预处理逻辑。

二、 项目应用

图片信息操作都是针对二进制数据Data来操作的,注(在图片转data和data转Image的时候,苹果会将部分exif信息抹除,所以如果需要保存数据到本地,最好通过data数据保存,上传图片数据到服务器,也建议通过data上传)。
废话不多说先上代码:

  1. 通过图片data数据获取图片图片资源引用。
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
  1. 再通过图片资源获取图片信息
    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);
  1. 接下来重头戏来了,也就是对图片信息进行编辑。
    首先对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];

  1. 保存添加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;
}

结语: 此篇文章的目的,主要是为了记录自己使用过的知识点,一来加深印象,二来也是为自己做下笔记。所以,文章大部分都以代码为主,读起来很是生涉,请谅解。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容