Saving Exif Data to Jpeg - Swift

Saving EXIF data to JPEG - Swift

My solution:

func toJpegWithExif(image: UIImage, metadata: NSDictionary, location: CLLocation?) -> Data? {
return autoreleasepool(invoking: { () -> Data in
let data = NSMutableData()
let options = metadata.mutableCopy() as! NSMutableDictionary
options[ kCGImageDestinationLossyCompressionQuality ] = CGFloat(Constants.JPEG_QUALITY)

// if location is available, add GPS data, thanks to https://gist.github.com/nitrag/343fe13f01bb0ef3692f2ae2dfe33e86
if ( nil != location ) {
let gpsData = NSMutableDictionary()

let altitudeRef = Int(location!.altitude < 0.0 ? 1 : 0)
let latitudeRef = location!.coordinate.latitude < 0.0 ? "S" : "N"
let longitudeRef = location!.coordinate.longitude < 0.0 ? "W" : "E"

// GPS metadata
gpsData[(kCGImagePropertyGPSLatitude as String)] = abs(location!.coordinate.latitude)
gpsData[(kCGImagePropertyGPSLongitude as String)] = abs(location!.coordinate.longitude)
gpsData[(kCGImagePropertyGPSLatitudeRef as String)] = latitudeRef
gpsData[(kCGImagePropertyGPSLongitudeRef as String)] = longitudeRef
gpsData[(kCGImagePropertyGPSAltitude as String)] = Int(abs(location!.altitude))
gpsData[(kCGImagePropertyGPSAltitudeRef as String)] = altitudeRef
gpsData[(kCGImagePropertyGPSTimeStamp as String)] = location!.timestamp.isoTime()
gpsData[(kCGImagePropertyGPSDateStamp as String)] = location!.timestamp.isoDate()
gpsData[(kCGImagePropertyGPSVersion as String)] = "2.2.0.0"

options[ kCGImagePropertyGPSDictionary as String ] = gpsData
}

let imageDestinationRef = CGImageDestinationCreateWithData(data as CFMutableData, kUTTypeJPEG, 1, nil)!
CGImageDestinationAddImage(imageDestinationRef, image.cgImage!, options)
CGImageDestinationFinalize(imageDestinationRef)
return data as Data
})
}

extension Date {

func isoDate() -> String {
let f = DateFormatter()
f.timeZone = TimeZone(abbreviation: "UTC")
f.dateFormat = "yyyy:MM:dd"
return f.string(from: self)
}

func isoTime() -> String {
let f = DateFormatter()
f.timeZone = TimeZone(abbreviation: "UTC")
f.dateFormat = "HH:mm:ss.SSSSSS"
return f.string(from: self)
}
}

The method converts the UIImage to a Jpeg with EXIF data from the camera plus an optional GPS location coming from CLLocationManager.

Save (override) my jpg file with editing EXIF data

After some test Im found a solution:

let fileURL = URL(fileURLWithPath: "/Users/test/Pictures/IMG_2808.jpg")

if let imageSource = CGImageSourceCreateWithURL(fileURL as CFURL, nil) {
var imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as Dictionary?
let exifDict = imageProperties?[kCGImagePropertyExifDictionary]

if let UserComment = exifDict?[kCGImagePropertyExifUserComment] ?? nil {
print("kCGImagePropertyExifUserComment: \(UserComment)")
}
else {
exifDict!.setValue("My comment...", forKey: kCGImagePropertyExifUserComment as String)
imageProperties![kCGImagePropertyExifDictionary] = exifDict

if let imageDestination = CGImageDestinationCreateWithURL(fileURL as CFURL, kUTTypeJPEG, 1, nil) {

CGImageDestinationAddImageFromSource(imageDestination, imageSource, 0, imageProperties as CFDictionary?)

CGImageDestinationFinalize(imageDestination)
}
}
}

I don't know if this solution is true but it works!
Please comment if you know more perfected way

How to write Exif data to image in Swift with lat long

Please check this below answer.
you got error due to nil value on EXIFDictionary and GPSDictionary

 var image = info[UIImagePickerControllerOriginalImage] as! UIImage
let jpeg = UIImageJPEGRepresentation(image, 1.0)
var source: CGImageSource? = nil
source = CGImageSourceCreateWithData((jpeg as CFData?)!, nil)
let metadata = CGImageSourceCopyPropertiesAtIndex(source!, 0, nil) as? [AnyHashable: Any]
var metadataAsMutable = metadata
var EXIFDictionary = (metadataAsMutable?[(kCGImagePropertyExifDictionary as String)]) as? [AnyHashable: Any]
var GPSDictionary = (metadataAsMutable?[(kCGImagePropertyGPSDictionary as String)]) as? [AnyHashable: Any]

if !(EXIFDictionary != nil) {
EXIFDictionary = [AnyHashable: Any]()
}
if !(GPSDictionary != nil) {
GPSDictionary = [AnyHashable: Any]()
}

GPSDictionary![(kCGImagePropertyGPSLatitude as String)] = 30.21313
GPSDictionary![(kCGImagePropertyGPSLongitude as String)] = 76.22346
EXIFDictionary![(kCGImagePropertyExifUserComment as String)] = "Hello Image"

let UTI: CFString = CGImageSourceGetType(source!)!
let dest_data = NSMutableData()
let destination: CGImageDestination = CGImageDestinationCreateWithData(dest_data as CFMutableData, UTI, 1, nil)!
CGImageDestinationAddImageFromSource(destination, source!, 0, (metadataAsMutable as CFDictionary?))
CGImageDestinationFinalize(destination)

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

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.

Swift: Custom camera save modified metadata with image

I ended up figuring out how to get everything to work the way I needed it to. The thing that helped me the most was finding out that a CFDictionary can be cast as a NSMutableDictionary.

Here is my final code:

As you can see I add a property to the EXIF dictionary for the date digitized, and changed the orientation value.

private func snapPhoto(success: (UIImage, NSMutableDictionary) -> Void, errorMessage: String -> Void) {
guard !self.stillImageOutput.capturingStillImage,
let videoConnection = stillImageOutput.connectionWithMediaType(AVMediaTypeVideo) else { return }

videoConnection.fixVideoOrientation()

stillImageOutput.captureStillImageAsynchronouslyFromConnection(videoConnection) {
(imageDataSampleBuffer, error) -> Void in
guard imageDataSampleBuffer != nil && error == nil else {
errorMessage("Couldn't snap photo")
return
}

let data = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer)

let rawMetadata = CMCopyDictionaryOfAttachments(nil, imageDataSampleBuffer, CMAttachmentMode(kCMAttachmentMode_ShouldPropagate))
let metadata = CFDictionaryCreateMutableCopy(nil, 0, rawMetadata) as NSMutableDictionary

let exifData = metadata.valueForKey(kCGImagePropertyExifDictionary as String) as? NSMutableDictionary
exifData?.setValue(NSDate().toString("yyyy:MM:dd HH:mm:ss"), forKey: kCGImagePropertyExifDateTimeDigitized as String)

metadata.setValue(exifData, forKey: kCGImagePropertyExifDictionary as String)
metadata.setValue(1, forKey: kCGImagePropertyOrientation as String)

guard let image = UIImage(data: data)?.fixOrientation() else {
errorMessage("Couldn't create image")
return
}

success(image, metadata)
}
}

And my final code for saving the image with the metadata:

Lots of guard statements, which I hate, but it is better than force unwrapping.

func saveImage(withMetadata image: UIImage, metadata: NSMutableDictionary) {
let filePath = "\(self.documentsPath)/image1.jpg"

guard let jpgData = UIImageJPEGRepresentation(image, 1) else { return }

// Add metadata to jpgData
guard let source = CGImageSourceCreateWithData(jpgData, nil),
let uniformTypeIdentifier = CGImageSourceGetType(source) else { return }
let finalData = NSMutableData(data: jpgData)
guard let destination = CGImageDestinationCreateWithData(finalData, uniformTypeIdentifier, 1, nil) else { return }
CGImageDestinationAddImageFromSource(destination, source, 0, metadata)
guard CGImageDestinationFinalize(destination) else { return }

// Save image that now has metadata
self.fileService.save(filePath, data: finalData)
}

Here is my updated save method (Not the exact same that I was using when I wrote this question, since I have updated to Swift 2.3, but the concept is the same):

public func save(fileAt path: NSURL, with data: NSData) throws -> Bool {
guard let pathString = path.absoluteString else { return false }
let directory = (pathString as NSString).stringByDeletingLastPathComponent

if !self.fileManager.fileExistsAtPath(directory) {
try self.makeDirectory(at: NSURL(string: directory)!)
}

if self.fileManager.fileExistsAtPath(pathString) {
try self.delete(fileAt: path)
}

return self.fileManager.createFileAtPath(pathString, contents: data, attributes: [NSFileProtectionKey: NSFileProtectionComplete])
}

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! ")

}


Related Topics



Leave a reply



Submit