Установка параметров exif-данных для изображения
Я использую новую среду ImageIO в iOS 4.1. Я успешно извлекаю метаданные exif, используя следующее:
CFDictionaryRef metadataDict = CMGetAttachment(sampleBuffer, kCGImagePropertyExifDictionary , NULL);
Прочитав это, он выглядит действительным. Сохранение изображения выполняется, но на изображении никогда нет никаких 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);
Ответы
Ответ 1
Следующее сообщение в блоге, где я получил свой ответ, когда у меня возникли проблемы с изменением и сохранением данных Exif Caffeinated Cocoa. Это работает на iOS.
Вот мой тестовый код для написания данных 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);
Ответ 2
Я попробовал ответить Стиву, и он работает, но я думаю, что он медленный для больших изображений, потому что он дублирует весь образ.
Вы можете установить свойства непосредственно на CMSampleBuffer, используя CMSetAttachments. Просто сделайте это, прежде чем называть 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);
И метод getGPSDictionaryForLocation: можно найти здесь:
Сохранение информации Geotag с фотографией на iOS4.1
Ответ 3
Я создал категорию MSMutableDictionary, чтобы помочь сохранить геотаг и другие метаданные для изображения. Посмотрите мой блог здесь:
http://blog.codecropper.com/2011/05/adding-metadata-to-ios-images-the-easy-way/
Ответ 4
Часть включает создание словаря метаданных GPS для данных EXIF. Здесь категория CLLocation, чтобы сделать именно это:
https://gist.github.com/phildow/6043486
Ответ 5
Поскольку я все еще учился, мне потребовалось некоторое время, чтобы собрать вместе ответы Стива и Марти, чтобы добавить метаданные к данным для изображения и впоследствии сохранить их. Ниже я выполнил Свои ответы, которые не используют методы ImageIO.
С учетом буфера выборки CMSampleBuffer buffer
, некоторой CLLocation location
и использования Mortys для использования 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
}
В этот момент вы можете записать данные изображения в файл или использовать API фотографий для сохранения изображения в Camera Roll (используя PHAssetCreationRequest addResource:with:data:options
для iOS 9+ или для более старых версий iOS, написав imageData на временный файл, а затем вызов PHAssetChangeRequest.creationRequestForAssetFromImage:atFileURL
). Обратите внимание, что ALAssertLibrary устарела для iOS 9. Я предоставляю больше деталей реализации в ответе здесь.
Ответ 6
SWIFT 5:
У меня ушла неделя на то, чтобы собрать все части в рабочий код.
Это сохранит временный файл UIImage
to JPEG с метаданными GPS:
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")
}
А вот расширение метаданных 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)
}
}
Это!
Теперь вы можете использовать activityViewController
чтобы сохранить временное изображение (используя temporaryFileURL
) в альбом фотографий, или сохранить его в виде файла, или поделиться им с другими приложениями или любым другим способом.
Ответ 7
[Пересмотр этого ответа из-за downvotes без объяснений.]
Apple обновила свою статью, посвященную этой проблеме (Technical Q & A QA1622). Если вы используете более старую версию Xcode, вы все равно можете иметь статью, в которой говорится, более или менее сложная удача, вы не можете сделать этого без низкоуровневого анализа данных изображения.
Обновленная версия находится здесь:
https://developer.apple.com/library/ios/#qa/qa1622/_index.html
Я адаптировал код следующим образом:
- (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];
}
и я называю это от
imagePickerController:didFinishPickingMediaWithInfo:
который является методом делегата для устройства выбора изображений. (То, где я поставил логику, чтобы увидеть, есть ли изображение для сохранения и т.д.)
Для полноты, здесь вспомогательный метод для получения данных 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;
}
См. также Записать UIImage вместе с метаданными (EXIF, GPS, TIFF) в библиотеке фотографий iPhone
Ответ 8
Таким образом, я устанавливаю данные 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);