保留图片原有的EXIF

作者: AlienJunX | 来源:发表于2016-11-22 11:05 被阅读974次

    在进行照片相关处理时,就会遇到如何保留原始照片的图像信息,一些图片分享社区通常会显示作者所使用的拍摄设备和曝光信息等等,来对所拍摄照片在技术参数的细节分享。

    什么是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) {
    }];
    
    保存到系统相册对比

    相关文章

      网友评论

      • seul:请问一下,我也遇到相同的问题,但我们需要传原图还需要带着日期等信息,
        UIImagePNGRepresentation(UIImage * image);
        UIImageJPEGRepresentation(UIImage * image, CGFloat compressionQuality);
        但这两个方法会去掉exif等信息,那怎么把imagePicker获取到的image转成NSData呢?因为我要用AFN上传图片,所以需要转Data。还是用其他上传方法呢?求助大神指点
        AlienJunX:@seul13148187 你不压缩图片就可以了, UIImagePNGRepresentation(UIImage * image); 之前取出meta信息,然后直接重写UIImagePNGRepresentation(UIImage * image); 出来的的nsdata,再自己看文中的代码,把压缩那部分换成你通过UIImagePNGRepresentation(UIImage * image); 得到的data就好了
        seul:@AlienJunX 谢谢回复,文中的方法我试了特别好使,但我意思文中说是压缩过的图片嘛,我意思不压缩直接传原图,是否有其他方法。
        AlienJunX:文中已经说清楚了代码也给出了,你仔细看看
      • 大东哥爱写Bug:其实在 “取出原图exif、tiff等信息” 那段代码里,直接使用`CFDictionaryRef imageMetaData = (__bridge CFDictionaryRef)image_representation.metadata`就可以了
        AlienJunX:@大东哥爱写Bug 多谢提醒

      本文标题:保留图片原有的EXIF

      本文链接:https://www.haomeiwen.com/subject/xcuppttx.html