在进行照片相关处理时,就会遇到如何保留原始照片的图像信息,一些图片分享社区通常会显示作者所使用的拍摄设备和曝光信息等等,来对所拍摄照片在技术参数的细节分享。
什么是EXIF
可交换图像文件格式常被简称为Exif(Exchangeable image file format),是专门为数码相机的照片设定的,可以记录数码照片的属性信息和拍摄数据。
更多详细介绍EXIF
原因
利用ImageIO可以打印一下metadata,下面是一张JPEG图片正常情况下的metadata:
{
ColorModel = RGB;
DPIHeight = 72;
DPIWidth = 72;
Depth = 8;
Orientation = 1;
PixelHeight = 1080;
PixelWidth = 1440;
ProfileName = "sRGB IEC61966-2.1";
"{Exif}" = {
ApertureValue = "2.52606882168926";
BrightnessValue = "2.87380073800738";
ColorSpace = 1;
ComponentsConfiguration = (
1,
2,
3,
0
);
DateTimeDigitized = "2016:01:31 23:44:07";
DateTimeOriginal = "2016:01:31 23:44:07";
ExifVersion = (
2,
2,
1
);
ExposureBiasValue = 0;
ExposureMode = 0;
ExposureProgram = 2;
ExposureTime = "0.05";
FNumber = "2.4";
Flash = 24;
FlashPixVersion = (
1,
0
);
FocalLenIn35mmFilm = 33;
FocalLength = "4.12";
ISOSpeedRatings = (
50
);
LensMake = Apple;
LensModel = "iPhone 5 back camera 4.12mm f/2.4";
LensSpecification = (
"4.12",
"4.12",
"2.4",
"2.4"
);
MeteringMode = 5;
PixelXDimension = 1440;
PixelYDimension = 1080;
SceneCaptureType = 0;
SceneType = 1;
SensingMethod = 2;
ShutterSpeedValue = "4.321956769055745";
SubjectArea = (
1631,
1223,
1795,
1077
);
SubsecTimeDigitized = 336;
SubsecTimeOriginal = 336;
WhiteBalance = 0;
};
"{JFIF}" = {
DensityUnit = 0;
JFIFVersion = (
1,
0,
1
);
XDensity = 72;
YDensity = 72;
};
"{TIFF}" = {
DateTime = "2016:01:31 23:44:07";
Make = Apple;
Model = "iPhone 5";
Orientation = 1;
ResolutionUnit = 2;
Software = "9.2";
XResolution = 72;
YResolution = 72;
};
}
可能还会有些GPS信息等.
很多时候我们拿到图片后客户端会需要进行一些处理,例如压缩,缩放等等,避免传输很大的图片浪费资源。这时候对图片的信息就会有损失, 进行数据转换时会用到如下函数:
UIImagePNGRepresentation(UIImage * image);
UIImageJPEGRepresentation(UIImage * image, CGFloat compressionQuality);
以上两个函数将UIImage转NSData 都是将图片质量进行压缩,在实际测试中发现是会将EXIF信息丢失。
UIImagePNGRepresentation
{
ColorModel = RGB;
Depth = 8;
PixelHeight = 3024;
PixelWidth = 4032;
ProfileName = "sRGB IEC61966-2.1";
"{PNG}" = {
Chromaticities = (
"0.3127",
"0.329",
"0.64",
"0.33",
"0.3",
"0.6000000000000001",
"0.15",
"0.06"
);
Gamma = "0.45455";
InterlaceType = 0;
sRGBIntent = 0;
};
}
UIImageJPEGRepresentation
{
ColorModel = RGB;
Depth = 8;
Orientation = 6;
PixelHeight = 3024;
PixelWidth = 4032;
ProfileName = "sRGB IEC61966-2.1";
"{Exif}" = {
ColorSpace = 1;
PixelXDimension = 4032;
PixelYDimension = 3024;
};
"{JFIF}" = {
DensityUnit = 0;
JFIFVersion = (
1,
0,
1
);
XDensity = 72;
YDensity = 72;
};
"{TIFF}" = {
Orientation = 6;
};
}
这就导致在上传图片时本身图片的EXIF信息就已经不对了。
那怎么解决上传图片时保留EXIF信息呢?
1、最简单粗暴的方式就是直接传原图,不进行任何处理,这样就不丢失了。(但这样图片动不动就好几M,很浪费资源)
2、将原图的EXIF信息取出,再设置给处理好的图片,最后传输图片到服务器的过程中不要将NSData转为UIImage即可,也就是说处理好后的图片不要在进行转换以免丢失EXIF信息。
Demo:
ALAsset *asset = ..;
UIImage *tempImg = [UIImage imageWithCGImage:asset.defaultRepresentation.fullScreenImage];
//保存到程序的缓存目录
[SysTool createFileExistsAtPath:kUploadCachePath];
NSString *fileName = [ImageTool createUploadFileName];
NSString *filePath = [NSString stringWithFormat:@"%@/%@",kUploadCachePath,fileName];
if ([ImageTool saveImageCompress:tempImg WithName:fileName path:kUploadCachePath]) {
[_photoList addObject:filePath];
}
ALAssetRepresentation *image_representation = [asset defaultRepresentation];
// 取出原图exif、tiff等信息
CFDictionaryRef imageMetaData = (__bridge CFDictionaryRef)image_representation.metadata;
// 压缩的图片重写exif/tiff信息
NSData *compressData = [NSData dataWithContentsOfFile:filePath];
NSData *exifData = [ImageTool createMetaData:compressData metaDic:(__bridge NSDictionary *)imageMetaData];
// 上传图片到服务器...
[Upload data:exifData];
ImageTool
+ (NSData *)createMetaData:(NSData *)imgData metaDic:(NSDictionary *)metaDic {
// 1原图exif/tiff信息
CFDictionaryRef exif = (CFDictionaryRef)CFDictionaryGetValue((__bridge CFDictionaryRef)metaDic, kCGImagePropertyExifDictionary);
CFDictionaryRef tiff = (CFDictionaryRef)CFDictionaryGetValue((__bridge CFDictionaryRef)metaDic, kCGImagePropertyTIFFDictionary);
// 2获取压缩的图片
CGImageSourceRef compressSource = CGImageSourceCreateWithData((__bridge CFDataRef)imgData, NULL);
CFDictionaryRef compressImageMetaData = CGImageSourceCopyPropertiesAtIndex(compressSource,0,NULL);
CFMutableDictionaryRef compressImageMetaDataMu = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 50, compressImageMetaData);
// 3将原来的exif/tiff等信息设置到压缩后的图片上
if (exif) {
CFDictionarySetValue(compressImageMetaDataMu, kCGImagePropertyExifDictionary, exif);
}
if (tiff) {
CFDictionarySetValue(compressImageMetaDataMu, kCGImagePropertyTIFFDictionary, tiff);
}
NSData *exifData = [self saveImageWithImageData:imgData Properties:(__bridge NSDictionary*)compressImageMetaDataMu];
return exifData;
}
// 将图片的exif信息写入到图片流
+ (NSData *)saveImageWithImageData:(NSData *)data Properties:(NSDictionary *)properties {
NSMutableDictionary *dataDic = [NSMutableDictionary dictionaryWithDictionary:properties];
// 设置properties属性
CGImageSourceRef imageRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
CFStringRef uti = CGImageSourceGetType(imageRef);
NSMutableData *data1 = [NSMutableData data];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)data1, uti, 1, NULL);
if (!destination) {
NSLog(@"error");
return nil;
}
CGImageDestinationAddImageFromSource(destination, imageRef, 0, (__bridge CFDictionaryRef)dataDic);
BOOL check = CGImageDestinationFinalize(destination);
if (!check) {
NSLog(@"error");
return nil;
}
CFRelease(destination);
CFRelease(uti);
return data1;
}
其他
当拍照保存到相册以往我们都是简单的调用UIImagePickerController,用系统相机拍照然后直接调用以下API也是会丢失EXIF:
UIImageWriteToSavedPhotosAlbum(UIImage *image, __nullable id completionTarget, __nullable SEL completionSelector, void * __nullable contextInfo);
所以可以调用ALAssetsLibrary的API:
// 保存到系统相册,并附带meta信息
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library writeImageToSavedPhotosAlbum:imageToSave.CGImage metadata:(__bridge NSDictionary*)metaDic completionBlock:^(NSURL *assetURL, NSError *error) {
}];