Напишите UIImage вместе с метаданными (EXIF, GPS, TIFF) в iPhone Photo library
Я разрабатываю проект, где требования:
- Пользователь откроет камеру через приложение
- При захвате изображения некоторые данные будут добавлены к метаданным захваченных изображений. Я прошел через некоторые из форумов. Я попытался закодировать эту логику. Наверное, я дошел до сути, но чего-то не хватает, поскольку я не могу видеть метаданные, которые я добавляю к изображению. Мой код:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)dictionary
{
[picker dismissModalViewControllerAnimated:YES];
NSData *dataOfImageFromGallery = UIImageJPEGRepresentation (image,0.5);
NSLog(@"Image length: %d", [dataOfImageFromGallery length]);
CGImageSourceRef source;
source = CGImageSourceCreateWithData((CFDataRef)dataOfImageFromGallery, NULL);
NSDictionary *metadata = (NSDictionary *) CGImageSourceCopyPropertiesAtIndex(source, 0, NULL);
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 -
//I am appending my custom data just to test the logic……..
[GPSDictionary setValue:[NSNumber numberWithFloat:1.1] forKey:(NSString*)kCGImagePropertyGPSLatitude];
[GPSDictionary setValue:[NSNumber numberWithFloat:2.2] forKey:(NSString*)kCGImagePropertyGPSLongitude];
[GPSDictionary setValue:@"lat_ref" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
[GPSDictionary setValue:@"lon_ref" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
[GPSDictionary setValue:[NSNumber numberWithFloat:3.3] forKey:(NSString*)kCGImagePropertyGPSAltitude];
[GPSDictionary setValue:[NSNumber numberWithShort:4.4] forKey:(NSString*)kCGImagePropertyGPSAltitudeRef];
[GPSDictionary setValue:[NSNumber numberWithFloat:5.5] forKey:(NSString*)kCGImagePropertyGPSImgDirection];
[GPSDictionary setValue:@"_headingRef" forKey:(NSString*)kCGImagePropertyGPSImgDirectionRef];
[EXIFDictionary setValue:@"xml_user_comment" 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);
NSMutableData *dest_data = [NSMutableData data];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((CFMutableDataRef) dest_data, UTI, 1, NULL);
if(!destination)
{
NSLog(@"--------- Could not create image destination---------");
}
CGImageDestinationAddImageFromSource(destination, source, 0, (CFDictionaryRef) metadataAsMutable);
BOOL success = NO;
success = CGImageDestinationFinalize(destination);
if(!success)
{
NSLog(@"-------- could not create data from image destination----------");
}
UIImage * image1 = [[UIImage alloc] initWithData:dest_data];
UIImageWriteToSavedPhotosAlbum (image1, self, nil, nil);
}
Пожалуйста, помогите мне сделать это и получить что-то положительное.
Посмотрите на последнюю строку, я сохраняю изображение с моими метаданными?
Изображение сохраняется в тот момент, но метаданные, которые я добавляю к нему, не сохраняются.
Спасибо заранее.
Ответы
Ответ 1
Функция: UIImageWriteToSavePhotosAlbum
записывает только данные изображения.
Вам нужно прочитать ALAssetsLibrary
Метод, который вы в конечном счете хотите вызвать:
ALAssetsLibrary *library = [[ALAssetsLibrary alloc]
[library writeImageToSavedPhotosAlbum:metadata:completionBlock];
Ответ 2
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 assets library
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
// Get the image metadata (EXIF & TIFF)
NSMutableDictionary * imageMetadata = [[info objectForKey:UIImagePickerControllerMediaMetadata] mutableCopy];
// add GPS data
CLLocation * loc = <•••>; // need a location here
if ( loc ) {
[imageMetadata setObject:[self gpsDictionaryForLocation:loc] forKey:(NSString*)kCGImagePropertyGPSDictionary];
}
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
[library writeImageToSavedPhotosAlbum:[imageToSave CGImage]
metadata:imageMetadata
completionBlock:imageWriteCompletionBlock];
[imageMetadata release];
[library release];
}
и я называю это от
imagePickerController:didFinishPickingMediaWithInfo:
который является методом делегата для выбора изображений.
Я использую вспомогательный метод (адаптированный из GusUtils) для создания словаря метаданных 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];
[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 autorelease];
}
Пока это хорошо работает для меня на устройствах iOS4 и iOS5.
Обновить: и iOS6/iOS7. Я построил простой проект, используя этот код:
https://github.com/5teev/MetaPhotoSave
Ответ 3
Для тех, кто приходит сюда, пытаясь сделать снимок с камерой в вашем приложении и сохраняя файл изображения в рулоне камеры с помощью метаданных GPS, у меня есть решение Swift, которое использует Фотографий API, поскольку ALAssetsLibrary устарел с iOS 9.0.
Как уже упоминалось rikster в этом , API-интерфейс Photos не вставляет данные местоположения непосредственно в файл изображения JPG, даже если вы установили свойство .location нового актива.
Учитывая буфер выборки CMSampleBuffer buffer
, некоторые CLLocation location
и используя предложение Mortys , чтобы использовать CMSetAttachments
, чтобы избежать дублирования изображения, мы может сделать следующее. Метод gpsMetadata
, расширяющий CLLocation, можно найти здесь.
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
}
// Now save this image to the Camera Roll (will save with GPS metadata embedded in the file)
self.savePhoto(withData: imageData, completion: completion)
Ниже приведен метод savePhoto
. Обратите внимание, что удобный addResource:with:data:options
метод доступен только в iOS 9. Если вы поддерживаете более раннюю iOS и хотите использовать API фотографий, тогда вы должны сделать временный файл, а затем создать ресурс из файла по этому URL-адресу, если вы хотите правильно внедрить метаданные GPS (PHAssetChangeRequest.creationRequestForAssetFromImage:atFileURL
). Только установка PHAssets.location НЕ будет вставлять ваши новые метаданные в сам фактический файл.
func savePhoto(withData data: Data, completion: (() -> Void)? = nil) {
// Note that using the Photos API .location property on a request does NOT embed GPS metadata into the image file itself
PHPhotoLibrary.shared().performChanges({
if #available(iOS 9.0, *) {
// For iOS 9+ we can skip the temporary file step and write the image data from the buffer directly to an asset
let request = PHAssetCreationRequest.forAsset()
request.addResource(with: PHAssetResourceType.photo, data: data, options: nil)
request.creationDate = Date()
} else {
// Fallback on earlier versions; write a temporary file and then add this file to the Camera Roll using the Photos API
let tmpURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent("tempPhoto").appendingPathExtension("jpg")
do {
try data.write(to: tmpURL)
let request = PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: tmpURL)
request?.creationDate = Date()
} catch {
// Error writing the data; photo is not appended to the camera roll
}
}
}, completionHandler: { _ in
DispatchQueue.main.async {
completion?()
}
})
}
Кроме:
Если вы просто хотите сохранить изображение с помощью метаданных GPS в свои временные файлы или документы (в отличие от камеры/фотобиблиотеки), вы можете пропустить с помощью API фотографий и напрямую написать imageData для URL.
// Write photo to temporary files with the GPS metadata embedded in the file
let tmpURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent("tempPhoto").appendingPathExtension("jpg")
do {
try data.write(to: tmpURL)
// Do more work here...
} catch {
// Error writing the data; handle it here
}
Ответ 4
Получение метаданных с изображения, снятого камерой, в приложении:
UIImage *pTakenImage= [info objectForKey:@"UIImagePickerControllerOriginalImage"];
NSMutableDictionary *imageMetadata = [[NSMutableDictionary alloc] initWithDictionary:[info objectForKey:UIImagePickerControllerMediaMetadata]];
теперь сохранить изображение в библиотеку с извлеченными метаданными:
ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init];
[library writeImageToSavedPhotosAlbum:[sourceImage CGImage] metadata:imageMetadata completionBlock:Nil];
[library release];
или хотите сохранить в локальный каталог
CGImageDestinationAddImageFromSource(destinationPath,sourceImage,0, (CFDictionaryRef)imageMetadata);
Ответ 5
Часть этого включает создание метаданных GPS. Вот категория на CLLocation, чтобы сделать именно это:
https://gist.github.com/phildow/6043486
Ответ 6
Проблема, которую мы пытаемся решить, заключается в следующем: пользователь только что сделал снимок с камерой UIImagePickerController. Мы получаем UIImage. Как мы сбрасываем метаданные в этот UIImage, когда мы сохраняем его в ролике камеры (фотобиблиотека), теперь, когда у нас нет рамки AssetsLibrary?
Ответ (насколько я могу разобраться): используйте среду ImageIO. Извлеките данные JPEG из UIImage, используйте его в качестве источника и запишите его и словарь метаданных в пункт назначения, и сохраните данные назначения в качестве PHAsset в рулоне камеры.
В этом примере im
- это UIImage, а meta
- словарь метаданных:
let jpeg = UIImageJPEGRepresentation(im, 1)!
let src = CGImageSourceCreateWithData(jpeg as CFData, nil)!
let data = NSMutableData()
let uti = CGImageSourceGetType(src)!
let dest = CGImageDestinationCreateWithData(data as CFMutableData, uti, 1, nil)!
CGImageDestinationAddImageFromSource(dest, src, 0, meta)
CGImageDestinationFinalize(dest)
let lib = PHPhotoLibrary.shared()
lib.performChanges({
let req = PHAssetCreationRequest.forAsset()
req.addResource(with: .photo, data: data as Data, options: nil)
})
Хороший способ протестировать - и общий прецедент - получить метаданные фотографии из словаря делегата info
UIImagePickerController через ключ UIImagePickerControllerMediaMetadata
и свернуть его в PHAsset, когда мы сохраним его в библиотеке фотографий.