Search code examples
iosobjective-cuiimagemetadataexif

Why can't I read EXIF from my loaded UIImage, but still can from NSData?


I have some EXIF stored in a UIImage that I store using NSFileManager writeToFile:atomically:.

Now, when I try to read back the EXIF metadata imageProperties here shows the metadata I expect:

NSData *data = [NSData dataWithContentsOfFile:filePath];
source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
imageProperties = CGImageSourceCopyPropertiesAtIndex(source, 0, NULL);

However, if I read the data as a UIImage first, the data is gone:

UIImage *writtenImage = [UIImage imageWithContentsOfFile:filePath];
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)UIImagePNGRepresentation(writtenImage), NULL);
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(source, 0, NULL);

Additionally, even on converting the data variable above to an image, it fails:

NSData *data = [NSData dataWithContentsOfFile:filePath];
UIImage *writtenImage = [UIImage imageWithData:data];
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)UIImagePNGRepresentation(writtenImage), NULL);
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(source, 0, NULL);

Can EXIF metadata only be ready from the NSData?


Solution

  • EXIF is a header for certain image files (containers like png, jpg, tif, etc..). When the files are unpacked, the image is a completely different thing - an array of data representing pixels, where EXIF header is no longer relevant. Thus, when you have an NSData object of an image container it's a completely different beast compared to the image data incapsulated in a UIImage object (EXIF header is just not present there).

    You can still deduce certain metadata and fill it yourself, but this has nothing to do with UIImage, in this case you just work with the containers (files) directly:

    typedef NS_CLOSED_ENUM(uint8_t, TDWEXIFOrientationValue) {
        TDWEXIFOrientationValueTopLeft = 1,
        TDWEXIFOrientationValueTopRight,
        TDWEXIFOrientationValueBottomRight,
        TDWEXIFOrientationValueBottomLeft,
        TDWEXIFOrientationValueLeftTop,
        TDWEXIFOrientationValueRightTop,
        TDWEXIFOrientationValueRightBottom,
        TDWEXIFOrientationValueLeftBotton,
    };
    
    - (void)writeEXIFHeader:(CGImageSourceRef)image {
        /* get the file type */
        CFStringRef UTI = CGImageSourceGetType(image);
        if (!UTI) {
            /* Handle Error Retrieving File Type Accordingly */
            return;
        }
    
        NSMutableData *imageData = [NSMutableData new];
        /* create an image destination for saving the file */
        CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, UTI, 1, NULL);
        if (!destination) {
            /* Handle Error Creating CGImageDestinationRef Accordingly */
            return;
        }
    
        const void *keys[] = {
            kCGImageDestinationDateTime,
            kCGImageDestinationOrientation,
            kCGImageDestinationMergeMetadata
        };
    
        TDWEXIFOrientationValue orientationValue = TDWEXIFOrientationValueTopLeft;
        const void *values[] = {
            CFDateCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent()), // Sets current date and time for the picture
            CFNumberCreate(kCFAllocatorDefault, kCFNumberCharType, &orientationValue), // Default orientation
            kCFBooleanTrue // merge exif metadata instead of overwriting it
        };
    
    
        /* create an CFDictionaryRef with the modified metadata */
        CFDictionaryRef exifData = CFDictionaryCreate(kCFAllocatorDefault, keys, values, sizeof(keys) / sizeof(keys[0]), NULL, NULL);
    
        /* add an image to the destination */
        CFErrorRef errorRef = nil;
        if (!CGImageDestinationCopyImageSource(destination, image, exifData, &errorRef)) {
            CFStringRef errorDescription = CFErrorCopyDescription(errorRef);
            /* Handle Error Creating Writing EXIF data Accordingly */
            CFRelease(errorDescription);
        }
    }