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

时间:2012-01-25 17:02:01

标签: iphone ios ios5 exif

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

4 个答案:

答案 0 :(得分:17)

我正在使用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];
}

使用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;
}

最后,这是我用来生成所需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;
}

希望它有所帮助。感谢Gustavo Ambrozio,Chiquis和其他几位SO成员,我能够把它拼凑起来并在我的项目中使用它。

答案 1 :(得分:15)

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

按照此处的答案写入包含完整元数据的文件:

Problem setting exif data for an image

不知道为什么这会被贬低,但这是方法:

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

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

块代码:

    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] ;

在此代码之后,您将在jpeg nsdata中获取图像,并在可变cfdictionary中获取该图像的对应字典。

现在你所要做的就是:

    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);

请注意我不是使用ALAssets保存,而是直接进入我选择的文件夹。

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

答案 2 :(得分:4)

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

首先创建一个ExifContainer:

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

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

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

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

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

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

答案 3 :(得分:0)

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