Swift How to Modify Exif Info in Images Taken from Mobile Camera

Swift how to modify exif info in images taken from mobile camera

Yes! Finally I made a trick to modify the EXIF info. At first, you can get EXIF info from info[UIImagePickerControllerMediaMetadata] and NSData without EXIF from picked UIImage by UIImageJPEGRepresentation. Then, you can create a new NSDictionary with modified EXIF info. After that, call my function in the following, you can get image NSData with modified EXIF!

func saveImageWithImageData(data: NSData, properties: NSDictionary, completion: (data: NSData, path: NSURL) -> Void) {

let imageRef: CGImageSourceRef = CGImageSourceCreateWithData((data as CFDataRef), nil)!
let uti: CFString = CGImageSourceGetType(imageRef)!
let dataWithEXIF: NSMutableData = NSMutableData(data: data)
let destination: CGImageDestinationRef = CGImageDestinationCreateWithData((dataWithEXIF as CFMutableDataRef), uti, 1, nil)!

CGImageDestinationAddImageFromSource(destination, imageRef, 0, (properties as CFDictionaryRef))
CGImageDestinationFinalize(destination)

var paths: [AnyObject] = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let savePath: String = paths[0].stringByAppendingPathComponent("exif.jpg")

let manager: NSFileManager = NSFileManager.defaultManager()
manager.createFileAtPath(savePath, contents: dataWithEXIF, attributes: nil)

completion(data: dataWithEXIF,path: NSURL(string: savePath)!)

print("image with EXIF info converting to NSData: Done! Ready to upload! ")

}

EXIF data read and write

I'm using this to get EXIF infos from an image file:

import ImageIO

let fileURL = theURLToTheImageFile
if let imageSource = CGImageSourceCreateWithURL(fileURL as CFURL, nil) {
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)
if let dict = imageProperties as? [String: Any] {
print(dict)
}
}

It gives you a dictionary containing various informations like the color profile - the EXIF info specifically is in dict["{Exif}"].

Modifying Image Metadata

I’m answering this myself since I found out why it wasn’t saving the data and it looks like this question can help others.

My code is correct, the only issue is that I wasn’t formatting the date properly. Since the date wasn’t in the correct format it was getting pruned by the frameworks. I formatted the date like this and it saved and displayed properly:

let formatter = DateFormatter()
formatter.dateFormat = "yyyy:MM:dd HH:mm:ss"
exif[ImagePropertyExifDateTimeDigitized] = formatter.string(from: Date())

This was in place of the line:

exif[ImagePropertyExifDateTimeDigitized] = Date() as CFDate

The output is now (again pruned to only the relevant properties):

unmodified properties
{
"{Exif}" = {
DateTimeDigitized = "2007:07:31 17:42:01";
DateTimeOriginal = "2007:07:31 17:42:01";
};
}

modified properties
{
"{Exif}" = {
DateTimeDigitized = "2017:05:12 01:04:14";
DateTimeOriginal = "2007:07:31 17:42:01";
};
}

saved properties
{
"{Exif}" = {
DateTimeDigitized = "2017:05:12 01:04:14";
DateTimeOriginal = "2007:07:31 17:42:01";
};
}

UIImagePickerController and extracting EXIF data from existing photos

The naughty way to do this is to traverse the UIImagePickerViewController's views and pick out the selected image in the delegate callback.

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {

id thumbnailView = [[[[[[[[[[picker.view subviews]
objectAtIndex:0] subviews]
objectAtIndex:0] subviews]
objectAtIndex:0] subviews]
objectAtIndex:0] subviews]
objectAtIndex:0];

NSString *fullSizePath = [[[thumbnailView selectedPhoto] fileGroup] pathForFullSizeImage];
NSString *thumbnailPath = [[[thumbnailView selectedPhoto] fileGroup] pathForThumbnailFile];

NSLog(@"%@ and %@", fullSizePath, thumbnailPath);

}

That will give you the path to the full size image, which you can then open with an EXIF library of your choice.

But, this calls a Private API and these method names will be detected by Apple if you submit this app. So don't do this, OK?

Save CUSTOM metadata in an image taken from AVFoundation in iOS

UIImage and CIImage don't hold all the metadata. In fact, converting it from NSData to either of those two formats will strip a lot of that metadata out, so IMHO, UIImage and CIImage should only be used if you are planning on displaying it in the UI or passing it to CoreImage.

You can read the image properties like this:

- (NSDictionary *)getImageProperties:(NSData *)imageData {
// get the original image metadata
CGImageSourceRef imageSourceRef = CGImageSourceCreateWithData((__bridge CFDataRef) imageData, NULL);
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSourceRef, 0, NULL);
NSDictionary *props = (__bridge_transfer NSDictionary *) properties;
CFRelease(imageSourceRef);

return props;
}

You can then convert your NSDictionary to a NSMutableDictionary:

NSDictionary *props = [self getImageProperties:imageData];
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:props];

After that, add whatever fields you would like. Note that EXIF data is found by grabbing the kCGImagePropertyExifDictionary, GPS data by the kCGImagePropertyGPSDictionary, and so on:

NSMutableDictionary *exifDictionary = properties[(__bridge NSString *) kCGImagePropertyExifDictionary];

Look at the CGImageProperties header file for all the keys available.

Okay, so now that you have made whatever changes you needed to the metadata, adding it back takes a couple of more steps:

// incoming image data
NSData *imageData;

// create the image ref
CGDataProviderRef imgDataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef) imageData);
CGImageRef imageRef = CGImageCreateWithJPEGDataProvider(imgDataProvider, NULL, true, kCGRenderingIntentDefault);

// the updated properties from above
NSMutableDictionary *mutableDictionary;

// create the new output data
CFMutableDataRef newImageData = CFDataCreateMutable(NULL, 0);
// my code assumes JPEG type since the input is from the iOS device camera
CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (__bridge CFStringRef) @"image/jpg", kUTTypeImage);
// create the destination
CGImageDestinationRef destination = CGImageDestinationCreateWithData(newImageData, type, 1, NULL);
// add the image to the destination
CGImageDestinationAddImage(destination, imageRef, (__bridge CFDictionaryRef) mutableDictionary);
// finalize the write
CGImageDestinationFinalize(destination);

// memory cleanup
CGDataProviderRelease(imgDataProvider);
CGImageRelease(imageRef);
CFRelease(type);
CFRelease(destination);

// your new image data
NSData *newImage = (__bridge_transfer NSData *)newImageData;

And there you have it.

I want to read exif info in image in android. I can read exif from image in gallery but i cannot read exif photo taken from the camera

You cannot read exif data from a Stream using the android api. ExifInterface doesn't have a constructor with an InputStream.

However, You can use the metadata-extractor
and use the constructor with an InputStream to build an InputStream backed by your byte[] using a ByteArrayInputStream

Reading the GPS data from the image returned by the camera in iOS iphone

One possibility is to leaving CoreLocation running when the camera is visible. Record each CCLocation into an array along with the time of the sample. When the photo comes back, find its time, then match the closest CClocation from the array.

Sounds kludgy but it will work.

Correctly set the right picture orientation when shooting photo

You may set your shot orientation to whatever you like by setting videoOrientation of your AVCapturePhotoOutput.

To match it with the current device orientation, you may use UIDevice.current.orientation manually converted to AVCaptureVideoOrientation.

let photoOutput = AVCapturePhotoOutput()

func takeShot() {

// set whatever orientation you like
let myShotOrientation = UIDevice.current.orientation.asCaptureVideoOrientation

if let photoOutputConnection = self.photoOutput.connection(with: .video) {
photoOutputConnection.videoOrientation = myShotOrientation
}

photoOutput.capturePhoto(...)
}

Conversion from UIDeviceOrientation to AVCaptureVideoOrientation:

extension UIDeviceOrientation {

///
var asCaptureVideoOrientation: AVCaptureVideoOrientation {
switch self {
// YES, that's not a mistake
case .landscapeLeft: return .landscapeRight
case .landscapeRight: return .landscapeLeft
case .portraitUpsideDown: return .portraitUpsideDown
default: return .portrait
}
}
}


Related Topics



Leave a reply



Submit