ios 如何将 exif 元数据写入图像(不是相机胶卷,只是 UIImage 或 JPEG)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9006759/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 16:29:50  来源:igfitidea点击:

How to write exif metadata to an image (not the camera roll, just a UIImage or JPEG)

iphoneiosios5exif

提问by akaru

I am aware of how to save metadata using ALAssets. But, I want to save an image, or upload it somewhere, with exif intact. I have exif data as an NSDictionary. But how can I inject it properly into a UIImage (or probably an NSData JPEG representation)?

我知道如何使用 ALAssets 保存元数据。但是,我想保存一个图像,或者将它上传到某个地方,exif 完好无损。我有作为 NSDictionary 的 exif 数据。但是如何将其正确注入 UIImage(或者可能是 NSData JPEG 表示)?

采纳答案by Pochi

UIImage does not contain metadata information (it is stripped). So if you want to save it without using the imagepicker method (not in camera roll):

UIImage 不包含元数据信息(它被剥离了)。所以如果你想在不使用 imagepicker 方法的情况下保存它(不在相机胶卷中):

Follow the answer here to write to a file with the metadata intact:

按照此处的答案写入元数据完好无损的文件:

Problem setting exif data for an image

为图像设置 exif 数据时出现问题

no idea why would this be downvoted but here is the method:

不知道为什么这会被否决,但这是方法:

In this case im getting the image through AVFoundation and this is what goes in the

在这种情况下,我通过 AVFoundation 获取图像,这就是

[[self stillImageOutput] captureStillImageAsynchronouslyFromConnection:videoConnection 
                                                     completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error) 
{
    // code here
}

block code:

块代码:

    CFDictionaryRef metaDict = CMCopyDictionaryOfAttachments(NULL, imageSampleBuffer, kCMAttachmentMode_ShouldPropagate);

    CFMutableDictionaryRef mutable = CFDictionaryCreateMutableCopy(NULL, 0, metaDict);

    // Create formatted date
    NSTimeZone      *timeZone   = [NSTimeZone timeZoneWithName:@"UTC"];
    NSDateFormatter *formatter  = [[NSDateFormatter alloc] init]; 
    [formatter setTimeZone:timeZone];
    [formatter setDateFormat:@"HH:mm:ss.SS"];

    // Create GPS Dictionary
    NSDictionary *gpsDict   = [NSDictionary dictionaryWithObjectsAndKeys:
                               [NSNumber numberWithFloat:fabs(loc.coordinate.latitude)], kCGImagePropertyGPSLatitude
                               , ((loc.coordinate.latitude >= 0) ? @"N" : @"S"), kCGImagePropertyGPSLatitudeRef
                               , [NSNumber numberWithFloat:fabs(loc.coordinate.longitude)], kCGImagePropertyGPSLongitude
                               , ((loc.coordinate.longitude >= 0) ? @"E" : @"W"), kCGImagePropertyGPSLongitudeRef
                               , [formatter stringFromDate:[loc timestamp]], kCGImagePropertyGPSTimeStamp
                               , [NSNumber numberWithFloat:fabs(loc.altitude)], kCGImagePropertyGPSAltitude
                               , nil];  

    // The gps info goes into the gps metadata part

    CFDictionarySetValue(mutable, kCGImagePropertyGPSDictionary, (__bridge void *)gpsDict);

    // Here just as an example im adding the attitude matrix in the exif comment metadata

    CMRotationMatrix m = att.rotationMatrix;
    GLKMatrix4 attMat = GLKMatrix4Make(m.m11, m.m12, m.m13, 0, m.m21, m.m22, m.m23, 0, m.m31, m.m32, m.m33, 0, 0, 0, 0, 1);

    NSMutableDictionary *EXIFDictionary = (__bridge NSMutableDictionary*)CFDictionaryGetValue(mutable, kCGImagePropertyExifDictionary);

    [EXIFDictionary setValue:NSStringFromGLKMatrix4(attMat) forKey:(NSString *)kCGImagePropertyExifUserComment];

    CFDictionarySetValue(mutable, kCGImagePropertyExifDictionary, (__bridge void *)EXIFDictionary);

    NSData *jpeg = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer] ;

After this code you will have your image in the jpeg nsdata and the correspoding dictionary for that image in the mutable cfdictionary.

在此代码之后,您将拥有 jpeg nsdata 中的图像以及可变 cfdictionary 中该图像的对应字典。

All you have to do now is:

您现在要做的就是:

    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)jpeg, NULL);

    CFStringRef UTI = CGImageSourceGetType(source); //this is the type of image (e.g., public.jpeg)

    NSMutableData *dest_data = [NSMutableData data];


    CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)dest_data,UTI,1,NULL);

    if(!destination) {
        NSLog(@"***Could not create image destination ***");
    }

    //add the image contained in the image source to the destination, overidding the old metadata with our modified metadata
    CGImageDestinationAddImageFromSource(destination,source,0, (CFDictionaryRef) mutable);

    //tell the destination to write the image data and metadata into our data object.
    //It will return false if something goes wrong
    BOOL success = CGImageDestinationFinalize(destination);

    if(!success) {
        NSLog(@"***Could not create data from image destination ***");
    }

    //now we have the data ready to go, so do whatever you want with it
    //here we just write it to disk at the same path we were passed

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
    NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents folder
    NSString *dataPath = [documentsDirectory stringByAppendingPathComponent:@"ImagesFolder"];

    NSError *error;
    if (![[NSFileManager defaultManager] fileExistsAtPath:dataPath])
        [[NSFileManager defaultManager] createDirectoryAtPath:dataPath withIntermediateDirectories:NO attributes:nil error:&error]; //Create folder

    //    NSString *imageName = @"ImageName";

    NSString *fullPath = [dataPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.jpg", name]]; //add our image to the path

    [dest_data writeToFile:fullPath atomically:YES];

    //cleanup

    CFRelease(destination);
    CFRelease(source);

Note how I'm not saving using the ALAssets but directly into a folder of my choice.

请注意我没有使用 ALAssets 而是直接保存到我选择的文件夹中。

Btw most of this code can be found in the link I posted at first.

顺便说一句,大部分代码都可以在我最初发布的链接中找到。

回答by dchakarov

I am using UIImagePickerController to get the image from the camera and my flow is a bit different than the one described by Chiquis. Here it is:

我正在使用 UIImagePickerController 从相机获取图像,我的流程与 Chiquis 描述的流程略有不同。这里是:

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
    UIImage *image = info[@"UIImagePickerControllerOriginalImage"];
    NSString *fullPhotoFilename = ...; // generate the photo name and path here
    NSData *photoData = [UIImage taggedImageData:image.jpegData metadata:info[@"UIImagePickerControllerMediaMetadata"] orientation:image.imageOrientation];
    [photoData writeToFile:fullPhotoFilename atomically:YES];
}

And using a UIImage category to put combine the image data with its metadata:

并使用 UIImage 类别将图像数据与其元数据组合在一起:

#import <ImageIO/ImageIO.h>
#import "UIImage+Tagging.h"
#import "LocationHelper.h"

@implementation UIImage (Tagging)

+ (NSData *)writeMetadataIntoImageData:(NSData *)imageData metadata:(NSMutableDictionary *)metadata {
    // create an imagesourceref
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef) imageData, NULL);

    // this is the type of image (e.g., public.jpeg)
    CFStringRef UTI = CGImageSourceGetType(source);

    // create a new data object and write the new image into it
    NSMutableData *dest_data = [NSMutableData data];
    CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)dest_data, UTI, 1, NULL);
    if (!destination) {
        NSLog(@"Error: Could not create image destination");
    }
    // add the image contained in the image source to the destination, overidding the old metadata with our modified metadata
    CGImageDestinationAddImageFromSource(destination, source, 0, (__bridge CFDictionaryRef) metadata);
    BOOL success = NO;
    success = CGImageDestinationFinalize(destination);
    if (!success) {
        NSLog(@"Error: Could not create data from image destination");
    }
    CFRelease(destination);
    CFRelease(source);
    return dest_data;
}

+ (NSData *)taggedImageData:(NSData *)imageData metadata:(NSDictionary *)metadata orientation:(UIImageOrientation)orientation {
    CLLocationManager *locationManager = [CLLocationManager new];
    CLLocation *location = [locationManager location];
    NSMutableDictionary *newMetadata = [NSMutableDictionary dictionaryWithDictionary:metadata];
    if (!newMetadata[(NSString *)kCGImagePropertyGPSDictionary] && location) {
        newMetadata[(NSString *)kCGImagePropertyGPSDictionary] = [LocationHelper gpsDictionaryForLocation:location];
    }

    // Reference: http://sylvana.net/jpegcrop/exif_orientation.html
    int newOrientation;
    switch (orientation) {
        case UIImageOrientationUp:
            newOrientation = 1;
            break;

        case UIImageOrientationDown:
            newOrientation = 3;
            break;

        case UIImageOrientationLeft:
            newOrientation = 8;
            break;

        case UIImageOrientationRight:
            newOrientation = 6;
            break;

        case UIImageOrientationUpMirrored:
            newOrientation = 2;
            break;

        case UIImageOrientationDownMirrored:
            newOrientation = 4;
            break;

        case UIImageOrientationLeftMirrored:
            newOrientation = 5;
            break;

        case UIImageOrientationRightMirrored:
            newOrientation = 7;
            break;

        default:
            newOrientation = -1;
    }
    if (newOrientation != -1) {
        newMetadata[(NSString *)kCGImagePropertyOrientation] = @(newOrientation);
    }
    NSData *newImageData = [self writeMetadataIntoImageData:imageData metadata:newMetadata];
    return newImageData;
}

And finally, here is the method I am using to generate the needed GPS dictionary:

最后,这是我用来生成所需 GPS 字典的方法:

+ (NSDictionary *)gpsDictionaryForLocation:(CLLocation *)location {
    NSTimeZone      *timeZone   = [NSTimeZone timeZoneWithName:@"UTC"];
    NSDateFormatter *formatter  = [[NSDateFormatter alloc] init];
    [formatter setTimeZone:timeZone];
    [formatter setDateFormat:@"HH:mm:ss.SS"];

    NSDictionary *gpsDict = @{(NSString *)kCGImagePropertyGPSLatitude: @(fabs(location.coordinate.latitude)),
                          (NSString *)kCGImagePropertyGPSLatitudeRef: ((location.coordinate.latitude >= 0) ? @"N" : @"S"),
                          (NSString *)kCGImagePropertyGPSLongitude: @(fabs(location.coordinate.longitude)),
                          (NSString *)kCGImagePropertyGPSLongitudeRef: ((location.coordinate.longitude >= 0) ? @"E" : @"W"),
                          (NSString *)kCGImagePropertyGPSTimeStamp: [formatter stringFromDate:[location timestamp]],
                          (NSString *)kCGImagePropertyGPSAltitude: @(fabs(location.altitude)),
                          };
    return gpsDict;
}

Hope it helps someone. Thanks to Gustavo Ambrozio, Chiquis and several others SO members I was able to piece it together and use it in my project.

希望它可以帮助某人。感谢 Gustavo Ambrozio、Chiquis 和其他几个 SO 成员,我能够将它拼凑起来并在我的项目中使用它。

回答by Nikita Took

There is easier way. If you need to save some exif, you can use SimpleExif pod

有更简单的方法。如果需要保存一些exif,可以使用SimpleExif pod

First create a ExifContainer:

首先创建一个 ExifContainer:

ExifContainer *container = [[ExifContainer alloc] init];

and populate it with all requred data:

并用所有需要的数据填充它:

[container addUserComment:@"A long time ago, in a galaxy far, far away"];
[container addCreationDate:[NSDate dateWithTimeIntervalSinceNow:-10000000]];
[container addLocation:locations[0]];

Then you can add this data to image:

然后您可以将此数据添加到图像:

NSData *imageData = [[UIImage imageNamed:@"DemoImage"] addExif:container];

Then you just save this data as a JPEG

然后您只需将此数据保存为 JPEG

回答by CGR

I faced the same problem, now I can upload files with EXIF data, also you can compress photo if need it, this solved the issue for me:

我遇到了同样的问题,现在我可以上传带有 EXIF 数据的文件,也可以根据需要压缩照片,这为我解决了问题:

// Get your image.
UIImage *loImgPhoto = [self getImageFromAsset:loPHAsset];

// Get your metadata (includes the EXIF data).
CGImageSourceRef loImageOriginalSource = CGImageSourceCreateWithData(( CFDataRef) loDataFotoOriginal, NULL);
NSDictionary *loDicMetadata = (__bridge NSDictionary *) CGImageSourceCopyPropertiesAtIndex(loImageOriginalSource, 0, NULL);

// Set your compression quality (0.0 to 1.0).
NSMutableDictionary *loDicMutableMetadata = [loDicMetadata mutableCopy];
[loDicMutableMetadata setObject:@(lfCompressionQualityValue) forKey:(__bridge NSString *)kCGImageDestinationLossyCompressionQuality];

// Create an image destination.
NSMutableData *loNewImageDataWithExif = [NSMutableData data];
CGImageDestinationRef loImgDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)loNewImageDataWithExif, CGImageSourceGetType(loImageOriginalSource), 1, NULL);


// Add your image to the destination.
CGImageDestinationAddImage(loImgDestination, loImgPhoto.CGImage, (__bridge CFDictionaryRef) loDicMutableMetadata);

// Finalize the destination.
if (CGImageDestinationFinalize(loImgDestination))
   {
       NSLog(@"Successful image creation.");                   
       // process the image rendering, adjustment data creation and finalize the asset edit.


       //Upload photo with EXIF metadata
       [self myUploadMethod:loNewImageDataWithExif];

    }
    else
    {
          NSLog(@"Error -> failed to finalize the image.");                         
    }

CFRelease(loImageOriginalSource);
CFRelease(loImgDestination);

getImageFromAssetmethod:

getImageFromAsset方法:

-(UIImage *)getImageFromAsset:(PHAsset *)aPHAsset

{
    __block  UIImage *limgImageResult;

    PHImageRequestOptions *lPHImageRequestOptions = [PHImageRequestOptions new];
    lPHImageRequestOptions.synchronous = YES;

    [self.imageManager requestImageForAsset:aPHAsset
                                 targetSize:PHImageManagerMaximumSize
                                contentMode:PHImageContentModeDefault//PHImageContentModeAspectFit
                                    options:lPHImageRequestOptions
                              resultHandler:^(UIImage *limgImage, NSDictionary *info) {

                                  limgImageResult = limgImage;
                              }];


    return limgImageResult;
}

回答by snakeoil

Here's the basics of setting Make and Model metadata on a .jpgfile in Swift 3 https://gist.github.com/lacyrhoades/09d8a367125b6225df5038aec68ed9e7The higher level versions, like using ExifContainer pod, did not work for me.

这是.jpg在 Swift 3 https://gist.github.com/lacyrhoades/09d8a367125b6225df5038aec68ed9e7 中的文件上设置 Make 和 Model 元数据的基础知识更高级别的版本,如使用 ExifContainer pod,对我不起作用。