Search code examples
iosobjective-cnsdataalassetslibrarycgimage

iOS overwrite EXIF flash property with new value for front camera flash


I'm trying to change the kCGImagePropertyExifFlash property of the Exif dictionary. My app offers front facing flash modes, so I'm trying to change this Exif value. No matter what I do when I go to save the newImageData using writeImageDataToSavedPhotosAlbum method of ALAssetsLibrary, it removes the value I set.

I'd appreciate any help offered.

[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:
[self.stillImageOutput connectionWithMediaType:AVMediaTypeVideo] 
completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error)
{
    if (imageSampleBuffer && !error)
    {
        CFDictionaryRef metadataDict = CMCopyDictionaryOfAttachments(NULL, imageSampleBuffer, kCMAttachmentMode_ShouldPropagate);

        NSDictionary *metadata = [NSDictionary dictionaryWithDictionary:(__bridge NSDictionary *)metadataDict];

        CFRelease(metadataDict);

        NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];

        if (metadata && imageData)
        {
            NSMutableDictionary *newMetaData = [NSMutableDictionary dictionaryWithDictionary:metadata];

            NSMutableDictionary *exifMetadata = [NSMutableDictionary dictionaryWithDictionary:
            [metadata objectForKey:(NSString *)kCGImagePropertyExifDictionary]];

            if (self.isUsingFrontCamera && exifMetadata)
            {
                if (self.flashButton.flashMode == On)
                {
                    [exifMetadata setObject:@"On, Fired" forKey:(NSString *)kCGImagePropertyExifFlash];
                }
                else if (self.flashButton.flashMode == Off)
                {
                    [exifMetadata setObject:@"Off, did not fire" forKey:(NSString *)kCGImagePropertyExifFlash];
                }
                else if (self.frontFlashAutoDidFire)
                {
                    [exifMetadata setObject:@"Auto, Fired" forKey:(NSString *)kCGImagePropertyExifFlash];
                }
             }

             [newMetaData setObject:exifMetadata forKey:(NSString *)kCGImagePropertyExifDictionary];

             NSData *newImageData = [self writeMetadataIntoImageData:imageData metadata:newMetaData];

             [ALAssetsLibrary.new writeImageDataToSavedPhotosAlbum:newImageData metadata:nil completionBlock:nil];
         }
     }
}

- (NSData *)writeMetadataIntoImageData:(NSData *)imageData metadata:(NSDictionary *)metadata
{
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef) imageData, NULL);

    CFStringRef UTI = CGImageSourceGetType(source);

    NSMutableData *data = [NSMutableData data];

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

    CGImageDestinationAddImageFromSource(destination, source, 0, (__bridge CFDictionaryRef) metadata);

    CGImageDestinationFinalize(destination);

    CFRelease(destination);
    CFRelease(source);

    return data;
}

Solution

  • I figured this out; it turns out the kCGImagePropertyExifFlash is an NSCFNumber, not an NSCFString, which was my problem. I was able to log the default integer values for the flash modes:

    • On, Fired is a value of 9
    • Off, did not fire is a value of 16
    • Auto, did not fire is a value of 24
    • Auto, Fired is a value of 25
    • If the hardware device doesn't have flash support, the value is 32
    • Unsure of any other possible values, perhaps related to when the flash isn't available if it's too warm.

    Code:

    [self.stillImageOutput captureStillImageAsynchronouslyFromConnection:
    [self.stillImageOutput connectionWithMediaType:AVMediaTypeVideo] 
    completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error)
    {
        if (imageSampleBuffer && !error)
        {
            CFDictionaryRef metadataDict = CMCopyDictionaryOfAttachments(NULL, imageSampleBuffer, kCMAttachmentMode_ShouldPropagate);
    
            NSDictionary *metadata = [NSDictionary dictionaryWithDictionary:(__bridge NSDictionary *)metadataDict];
    
            CFRelease(metadataDict);
    
            NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
    
            if (metadata && imageData)
            {
                NSMutableDictionary *newMetaData = [NSMutableDictionary dictionaryWithDictionary:metadata];
    
                NSMutableDictionary *exifMetadata = [NSMutableDictionary dictionaryWithDictionary:
                [metadata objectForKey:(NSString *)kCGImagePropertyExifDictionary]];
    
                if (self.isUsingFrontCamera && exifMetadata)
                {
                    if (self.flashButton.flashMode == On)
                    {
                        [exifMetadata setObject:@(9) forKey:(NSString *)kCGImagePropertyExifFlash];
                    }
                    else if (self.flashButton.flashMode == Off)
                    {
                        [exifMetadata setObject:@(16) forKey:(NSString *)kCGImagePropertyExifFlash];
                    }
                    else if (self.flashButton.flashMode == Auto)
                    {
                        [exifMetadata setObject:@(24) forKey:(NSString *)kCGImagePropertyExifFlash];
    
                        if (self.frontFlashAutoDidFire)
                        {
                            [exifMetadata setObject:@(25) forKey:(NSString *)kCGImagePropertyExifFlash];
                        }
                    }
                }
    
                [newMetaData setObject:exifMetadata forKey:(NSString *)kCGImagePropertyExifDictionary];
    
                [ALAssetsLibrary.new writeImageDataToSavedPhotosAlbum:imageData metadata:newMetaData completionBlock:nil];
            }
        }
    }