macos 为图像设置 exif 数据时出现问题
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5125323/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Problem setting exif data for an image
提问by akaru
I'm using the new ImageIO framework in iOS 4.1. I successfully retrieve the exif metadata using the following:
我在 iOS 4.1 中使用新的 ImageIO 框架。我使用以下方法成功检索了 exif 元数据:
CFDictionaryRef metadataDict = CMGetAttachment(sampleBuffer, kCGImagePropertyExifDictionary , NULL);
Reading it out, it appears valid. Saving an image out works, but there is never any exif data in the image.
读出来,它似乎是有效的。保存图像有效,但图像中永远不会有任何 exif 数据。
CGImageDestinationRef myImageDest = CGImageDestinationCreateWithURL((CFURLRef) docurl, kUTTypeJPEG, 1, NULL);
// Add the image to the destination using previously saved options.
CGImageDestinationAddImage(myImageDest, iref, NULL);
//add back exif
NSDictionary *props = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:.1], kCGImageDestinationLossyCompressionQuality,
metadataDict, kCGImagePropertyExifDictionary, //the exif metadata
nil];
//kCGImagePropertyExifAuxDictionary
CGImageDestinationSetProperties(myImageDest, (CFDictionaryRef) props);
// Finalize the image destination.
bool status = CGImageDestinationFinalize(myImageDest);
回答by Steve McFarlin
The following blog post is where I got my answer when I had issues with modifying and saving Exif data Caffeinated Cocoa. This works on iOS.
以下博客文章是我在修改和保存 Exif 数据 Caffeinated Cocoa 时遇到问题时得到答案的地方。这适用于iOS。
Here is my test code for writing Exif and GPS data. It pretty much a copy and paste of the code from the above blog. I am using this to write exif data to a captured image.
这是我编写 Exif 和 GPS 数据的测试代码。它几乎是上述博客中代码的复制和粘贴。我正在使用它来将 exif 数据写入捕获的图像。
NSData *jpeg = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer] ;
CGImageSourceRef source ;
source = CGImageSourceCreateWithData((CFDataRef)jpeg, NULL);
//get all the metadata in the image
NSDictionary *metadata = (NSDictionary *) CGImageSourceCopyPropertiesAtIndex(source,0,NULL);
//make the metadata dictionary mutable so we can add properties to it
NSMutableDictionary *metadataAsMutable = [[metadata mutableCopy]autorelease];
[metadata release];
NSMutableDictionary *EXIFDictionary = [[[metadataAsMutable objectForKey:(NSString *)kCGImagePropertyExifDictionary]mutableCopy]autorelease];
NSMutableDictionary *GPSDictionary = [[[metadataAsMutable objectForKey:(NSString *)kCGImagePropertyGPSDictionary]mutableCopy]autorelease];
if(!EXIFDictionary) {
//if the image does not have an EXIF dictionary (not all images do), then create one for us to use
EXIFDictionary = [NSMutableDictionary dictionary];
}
if(!GPSDictionary) {
GPSDictionary = [NSMutableDictionary dictionary];
}
//Setup GPS dict
[GPSDictionary setValue:[NSNumber numberWithFloat:_lat] forKey:(NSString*)kCGImagePropertyGPSLatitude];
[GPSDictionary setValue:[NSNumber numberWithFloat:_lon] forKey:(NSString*)kCGImagePropertyGPSLongitude];
[GPSDictionary setValue:lat_ref forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
[GPSDictionary setValue:lon_ref forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
[GPSDictionary setValue:[NSNumber numberWithFloat:_alt] forKey:(NSString*)kCGImagePropertyGPSAltitude];
[GPSDictionary setValue:[NSNumber numberWithShort:alt_ref] forKey:(NSString*)kCGImagePropertyGPSAltitudeRef];
[GPSDictionary setValue:[NSNumber numberWithFloat:_heading] forKey:(NSString*)kCGImagePropertyGPSImgDirection];
[GPSDictionary setValue:[NSString stringWithFormat:@"%c",_headingRef] forKey:(NSString*)kCGImagePropertyGPSImgDirectionRef];
[EXIFDictionary setValue:xml forKey:(NSString *)kCGImagePropertyExifUserComment];
//add our modified EXIF data back into the image's metadata
[metadataAsMutable setObject:EXIFDictionary forKey:(NSString *)kCGImagePropertyExifDictionary];
[metadataAsMutable setObject:GPSDictionary forKey:(NSString *)kCGImagePropertyGPSDictionary];
CFStringRef UTI = CGImageSourceGetType(source); //this is the type of image (e.g., public.jpeg)
//this will be the data CGImageDestinationRef will write into
NSMutableData *dest_data = [NSMutableData data];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((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) metadataAsMutable);
//tell the destination to write the image data and metadata into our data object.
//It will return false if something goes wrong
BOOL success = NO;
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
[dest_data writeToFile:file atomically:YES];
//cleanup
CFRelease(destination);
CFRelease(source);
回答by Morty
I tried Steve's answer and it works, but I think it's slow for large images because it's duplicating the entire image.
我尝试了史蒂夫的答案并且它有效,但我认为大图像很慢,因为它复制了整个图像。
You can set the properties directly on the CMSampleBuffer using CMSetAttachments. Just do this before calling jpegStillImageNSDataRepresentation
您可以使用 CMSetAttachments 直接在 CMSampleBuffer 上设置属性。在打电话之前这样做jpegStillImageNSDataRepresentation
CFDictionaryRef metaDict = CMCopyDictionaryOfAttachments(NULL, imageSampleBuffer, kCMAttachmentMode_ShouldPropagate);
CFMutableDictionaryRef mutable = CFDictionaryCreateMutableCopy(NULL, 0, metaDict);
NSMutableDictionary * mutableGPS = [self getGPSDictionaryForLocation:self.myLocation];
CFDictionarySetValue(mutable, kCGImagePropertyGPSDictionary, mutableGPS);
// set the dictionary back to the buffer
CMSetAttachments(imageSampleBuffer, mutable, kCMAttachmentMode_ShouldPropagate);
And the method getGPSDictionaryForLocation: can be found here:
方法 getGPSDictionaryForLocation: 可以在这里找到:
回答by Gustavo Ambrozio
I created a MSMutableDictionary category to help save geotag and other metadata to an image. Check out my blog post here:
我创建了一个 MSMutableDictionary 类别来帮助将地理标记和其他元数据保存到图像中。在这里查看我的博客文章:
http://blog.codecropper.com/2011/05/adding-metadata-to-ios-images-the-easy-way/
http://blog.codecropper.com/2011/05/adding-metadata-to-ios-images-the-easy-way/
回答by Undrea
Since I'm still learning, it took me a while to piece together both Steve and Marty's answers to add metadata to the Data for an image and subsequently save it. Below, I've made a Swift implementation of their answers that does not use ImageIO methods.
由于我还在学习,所以我花了一些时间将 Steve 和 Marty 的答案拼凑起来,将元数据添加到图像的数据中,然后保存它。下面,我对不使用 ImageIO 方法的答案进行了 Swift 实现。
Given a CMSampleBuffer sample buffer buffer
, some CLLocation location
, and using Morty's suggestionto use CMSetAttachments
in order to avoid duplicating the image, we can do the following. The gpsMetadata
method extending CLLocation can be found here(also Swift).
给定一个 CMSampleBuffer 样本缓冲区buffer
,一些 CLLocation location
,并使用 Morty 的建议使用CMSetAttachments
以避免复制图像,我们可以执行以下操作。gpsMetadata
可以在此处找到扩展 CLLocation的方法(也是 Swift)。
if let location = location {
// Get the existing metadata dictionary (if there is one)
var metaDict = CMCopyDictionaryOfAttachments(nil, buffer, kCMAttachmentMode_ShouldPropagate) as? Dictionary<String, Any> ?? [:]
// Append the GPS metadata to the existing metadata
metaDict[kCGImagePropertyGPSDictionary as String] = location.gpsMetadata()
// Save the new metadata back to the buffer without duplicating any data
CMSetAttachments(buffer, metaDict as CFDictionary, kCMAttachmentMode_ShouldPropagate)
}
// Get JPG image Data from the buffer
guard let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(buffer) else {
// There was a problem; handle it here
}
At this point, you can write the image data to a file or use the Photos API to save the image to the Camera Roll (using PHAssetCreationRequest's addResource:with:data:options
for iOS 9+, or for older iOS versions by writing the imageData to a temporary file and then calling PHAssetChangeRequest.creationRequestForAssetFromImage:atFileURL
). Note that ALAssertLibrary is deprecated for iOS 9. I provide more implementation details in an answer here.
此时,您可以将图像数据写入文件或使用照片 API 将图像保存到相机胶卷(PHAssetCreationRequest's addResource:with:data:options
用于 iOS 9+,或者对于较旧的 iOS 版本,通过将 imageData 写入临时文件然后调用PHAssetChangeRequest.creationRequestForAssetFromImage:atFileURL
) . 请注意,iOS 9 不推荐使用 ALAssertLibrary。我在此处的答案中提供了更多实现细节。
回答by lenooh
SWIFT 5:
快速 5:
It took me a week of scoping around to gather all the pieces into working code.
我花了一周的时间来确定范围,才将所有部分收集到可工作的代码中。
This will save an UIImage
to JPEG temp file with GPS metadata:
这将保存一个UIImage
带有 GPS 元数据的 JPEG 临时文件:
let image:UIImage = mImageView.image! // your UIImage
// create filename
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy.MM.dd-HH.mm.ss"
let now = Date()
let date_time = dateFormatter.string(from: now)
let fileName:String = "your_image_"+date_time+".jpg" // name your file the way you want
let temporaryFolder:URL = FileManager.default.temporaryDirectory
let temporaryFileURL:URL = temporaryFolder.appendingPathComponent(fileName)
// save the image to chosen path
let jpeg = image.jpegData(compressionQuality: 0.85)! // set JPG quality here (1.0 is best)
let src = CGImageSourceCreateWithData(jpeg as CFData, nil)!
let uti = CGImageSourceGetType(src)!
let cfPath = CFURLCreateWithFileSystemPath(nil, temporaryFileURL.path as CFString, CFURLPathStyle.cfurlposixPathStyle, false)
let dest = CGImageDestinationCreateWithURL(cfPath!, uti, 1, nil)
// create GPS metadata from current location
let gpsMeta = gCurrentLocation?.exifMetadata() // gCurrentLocation is your CLLocation (exifMetadata is an extension)
let tiffProperties = [
kCGImagePropertyTIFFMake as String: "Camera vendor",
kCGImagePropertyTIFFModel as String: "Camera model"
// --(insert other properties here if required)--
] as CFDictionary
let properties = [
kCGImagePropertyTIFFDictionary as String: tiffProperties,
kCGImagePropertyGPSDictionary: gpsMeta as Any
// --(insert other dictionaries here if required)--
] as CFDictionary
CGImageDestinationAddImageFromSource(dest!, src, 0, properties)
if (CGImageDestinationFinalize(dest!)) {
print("Saved image with metadata!")
} else {
print("Error saving image with metadata")
}
And here is the GPS metadata extension (from https://gist.github.com/chefren/8b50652d67c397a825619f83c8dba6d3):
这是 GPS 元数据扩展(来自https://gist.github.com/chefren/8b50652d67c397a825619f83c8dba6d3):
import Foundation
import CoreLocation
extension CLLocation {
func exifMetadata(heading:CLHeading? = nil) -> NSMutableDictionary {
let GPSMetadata = NSMutableDictionary()
let altitudeRef = Int(self.altitude < 0.0 ? 1 : 0)
let latitudeRef = self.coordinate.latitude < 0.0 ? "S" : "N"
let longitudeRef = self.coordinate.longitude < 0.0 ? "W" : "E"
// GPS metadata
GPSMetadata[(kCGImagePropertyGPSLatitude as String)] = abs(self.coordinate.latitude)
GPSMetadata[(kCGImagePropertyGPSLongitude as String)] = abs(self.coordinate.longitude)
GPSMetadata[(kCGImagePropertyGPSLatitudeRef as String)] = latitudeRef
GPSMetadata[(kCGImagePropertyGPSLongitudeRef as String)] = longitudeRef
GPSMetadata[(kCGImagePropertyGPSAltitude as String)] = Int(abs(self.altitude))
GPSMetadata[(kCGImagePropertyGPSAltitudeRef as String)] = altitudeRef
GPSMetadata[(kCGImagePropertyGPSTimeStamp as String)] = self.timestamp.isoTime()
GPSMetadata[(kCGImagePropertyGPSDateStamp as String)] = self.timestamp.isoDate()
GPSMetadata[(kCGImagePropertyGPSVersion as String)] = "2.2.0.0"
if let heading = heading {
GPSMetadata[(kCGImagePropertyGPSImgDirection as String)] = heading.trueHeading
GPSMetadata[(kCGImagePropertyGPSImgDirectionRef as String)] = "T"
}
return GPSMetadata
}
}
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)
}
}
That's it!
而已!
You can now use activityViewController
to save the temp image (using temporaryFileURL
) to photos album, or save it as a file, or share it to other apps, or whatever you want.
您现在可以使用activityViewController
将临时图像(使用temporaryFileURL
)保存到相册,或将其保存为文件,或共享到其他应用程序,或任何您想要的。
回答by Philip
A piece involves creating the GPS metadata dictionary for the EXIF data. Here's a CLLocation category to do just that:
一部分涉及为 EXIF 数据创建 GPS 元数据字典。这是一个 CLLocation 类别来做到这一点:
回答by CGR
In this way I set EXIF data, also you can compress photo if need it, this solved the issue for me: Hope it helps
通过这种方式我设置了 EXIF 数据,如果需要你也可以压缩照片,这为我解决了这个问题:希望它有帮助
// Get your image.
NSURL *url = @"http://somewebsite.com/path/to/some/image.jpg";
UIImage *loImgPhoto = [NSData dataWithContentsOfURL:url];
// 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);
回答by Code Roadie
[Revisiting this answer because of downvotes without explanations.]
[由于没有解释的downvotes而重新审视这个答案。]
Apple has updated their article addressing this issue (Technical Q&A QA1622). If you're using an older version of Xcode, you may still have the article that says, more or less, tough luck, you can't do this without low-level parsing of the image data.
Apple 更新了他们的文章来解决这个问题(技术问答 QA1622)。如果您使用的是旧版本的 Xcode,您可能仍然有文章说,或多或少,运气不好,如果没有对图像数据进行低级解析,您将无法做到这一点。
The updated version is here:
更新版本在这里:
https://developer.apple.com/library/ios/#qa/qa1622/_index.html
https://developer.apple.com/library/ios/#qa/qa1622/_index.html
I adapted the code there as follows:
我修改了那里的代码如下:
- (void) saveImage:(UIImage *)imageToSave withInfo:(NSDictionary *)info
{
// Get the image metadata (EXIF & TIFF)
NSMutableDictionary * imageMetadata = [[info objectForKey:UIImagePickerControllerMediaMetadata] mutableCopy];
// add (fake) GPS data
CLLocationCoordinate2D coordSF = CLLocationCoordinate2DMake(37.732711,-122.45224);
// arbitrary altitude and accuracy
double altitudeSF = 15.0;
double accuracyHorizontal = 1.0;
double accuracyVertical = 1.0;
NSDate * nowDate = [NSDate date];
// create CLLocation for image
CLLocation * loc = [[CLLocation alloc] initWithCoordinate:coordSF altitude:altitudeSF horizontalAccuracy:accuracyHorizontal verticalAccuracy:accuracyVertical timestamp:nowDate];
// this is in case we try to acquire actual location instead of faking it with the code right above
if ( loc ) {
[imageMetadata setObject:[self gpsDictionaryForLocation:loc] forKey:(NSString*)kCGImagePropertyGPSDictionary];
}
// Get the assets library
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
// create a completion block for when we process the image
ALAssetsLibraryWriteImageCompletionBlock imageWriteCompletionBlock =
^(NSURL *newURL, NSError *error) {
if (error) {
NSLog( @"Error writing image with metadata to Photo Library: %@", error );
} else {
NSLog( @"Wrote image %@ with metadata %@ to Photo Library",newURL,imageMetadata);
}
};
// Save the new image to the Camera Roll, using the completion block defined just above
[library writeImageToSavedPhotosAlbum:[imageToSave CGImage]
metadata:imageMetadata
completionBlock:imageWriteCompletionBlock];
}
and I call this from
我从
imagePickerController:didFinishPickingMediaWithInfo:
which is the delegate method for the image picker. (That's where I put the logic to see if there is an image to save, etc.)
这是图像选择器的委托方法。(这就是我放置逻辑以查看是否有要保存的图像等的地方。)
For completeness, here's the helper method to get the GPS data as a dictionary:
为了完整起见,以下是将 GPS 数据作为字典获取的辅助方法:
- (NSDictionary *) gpsDictionaryForLocation:(CLLocation *)location
{
CLLocationDegrees exifLatitude = location.coordinate.latitude;
CLLocationDegrees exifLongitude = location.coordinate.longitude;
NSString * latRef;
NSString * longRef;
if (exifLatitude < 0.0) {
exifLatitude = exifLatitude * -1.0f;
latRef = @"S";
} else {
latRef = @"N";
}
if (exifLongitude < 0.0) {
exifLongitude = exifLongitude * -1.0f;
longRef = @"W";
} else {
longRef = @"E";
}
NSMutableDictionary *locDict = [[NSMutableDictionary alloc] init];
// requires ImageIO
[locDict setObject:location.timestamp forKey:(NSString*)kCGImagePropertyGPSTimeStamp];
[locDict setObject:latRef forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
[locDict setObject:[NSNumber numberWithFloat:exifLatitude] forKey:(NSString *)kCGImagePropertyGPSLatitude];
[locDict setObject:longRef forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
[locDict setObject:[NSNumber numberWithFloat:exifLongitude] forKey:(NSString *)kCGImagePropertyGPSLongitude];
[locDict setObject:[NSNumber numberWithFloat:location.horizontalAccuracy] forKey:(NSString*)kCGImagePropertyGPSDOP];
[locDict setObject:[NSNumber numberWithFloat:location.altitude] forKey:(NSString*)kCGImagePropertyGPSAltitude];
return locDict;
}
See also Write UIImage along with metadata (EXIF, GPS, TIFF) in iPhone's Photo library
回答by Anirudha Mahale
Translated Steve McFarlin's answerto Swift & Wrapped it inside a class.
翻译 Steve McFarlin对 Swift的回答并将其包装在课堂中。
class GeoTagImage {
/// Writes GPS data into the meta data.
/// - Parameters:
/// - data: Coordinate meta data will be written to the copy of this data.
/// - coordinate: Cooordinates to write to meta data.
static func mark(_ data: Data, with coordinate: Coordinate) -> Data {
var source: CGImageSource? = nil
source = CGImageSourceCreateWithData((data as CFData?)!, nil)
// Get all the metadata in the image
let metadata = CGImageSourceCopyPropertiesAtIndex(source!, 0, nil) as? [AnyHashable: Any]
// Make the metadata dictionary mutable so we can add properties to it
var metadataAsMutable = metadata
var EXIFDictionary = (metadataAsMutable?[(kCGImagePropertyExifDictionary as String)]) as? [AnyHashable: Any]
var GPSDictionary = (metadataAsMutable?[(kCGImagePropertyGPSDictionary as String)]) as? [AnyHashable: Any]
if !(EXIFDictionary != nil) {
// If the image does not have an EXIF dictionary (not all images do), then create one.
EXIFDictionary = [:]
}
if !(GPSDictionary != nil) {
GPSDictionary = [:]
}
// add coordinates in the GPS Dictionary
GPSDictionary![(kCGImagePropertyGPSLatitude as String)] = coordinate.latitude
GPSDictionary![(kCGImagePropertyGPSLongitude as String)] = coordinate.longitude
EXIFDictionary![(kCGImagePropertyExifUserComment as String)] = "Raw Image"
// Add our modified EXIF data back into the image's metadata
metadataAsMutable!.updateValue(GPSDictionary!, forKey: kCGImagePropertyGPSDictionary)
metadataAsMutable!.updateValue(EXIFDictionary!, forKey: kCGImagePropertyExifDictionary)
// This is the type of image (e.g., public.jpeg)
let UTI: CFString = CGImageSourceGetType(source!)!
// This will be the data CGImageDestinationRef will write into
let dest_data = NSMutableData()
let destination: CGImageDestination = CGImageDestinationCreateWithData(dest_data as CFMutableData, UTI, 1, nil)!
// Add the image contained in the image source to the destination, overidding the old metadata with our modified metadata
CGImageDestinationAddImageFromSource(destination, source!, 0, (metadataAsMutable as CFDictionary?))
// Tells the destination to write the image data and metadata into our data object.
// It will return false if something goes wrong
_ = CGImageDestinationFinalize(destination)
return (dest_data as Data)
}
/// Prints the Meta Data from the Data.
/// - Parameter data: Meta data will be printed of this object.
static func logMetaData(from data: Data) {
if let imageSource = CGImageSourceCreateWithData(data as CFData, nil) {
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)
if let dict = imageProperties as? [String: Any] {
print(dict)
}
}
}
}