Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 584
akaru Avatar asked Jan 25 '12 17:01

akaru


People also ask

How do I put EXIF on a photo?

You need first to copy the exif files located here google exif to your project, then use the following code : ExifInterface exif = new ExifInterface(); exif.

What file formats support EXIF?

The exchangeable image file format (EXIF) is a standard for embedding technical metadata in image files that many camera manufacturers use and many image-processing programs support. EXIF metadata can be embedded in TIFF and JPEG images.

How do you write an EXIF?

To write Exif data to a JPEG, you need to write an APP1/Exif segment as part of the normal JIF structure. The EXIFWriter will write the data you should put inside this segment only. Everything else must be provided by you.


4 Answers

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

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

Problem setting exif data for an image

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

[[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.

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.

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

like image 43
Pochi Avatar answered Oct 09 '22 14:10

Pochi


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:

- (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:

#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:

+ (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.

like image 160
dchakarov Avatar answered Oct 09 '22 14:10

dchakarov


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

First create a 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

like image 4
Nikita Took Avatar answered Oct 09 '22 12:10

Nikita Took


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:

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

getImageFromAsset method:

-(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;
}
like image 1
CGR Avatar answered Oct 09 '22 14:10

CGR