Проблема с настройкой данных 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);
7 ответов:
в следующем блоге я получил свой ответ, когда у меня возникли проблемы с изменением и сохранением данных Exif с кофеином какао. Это работает на 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);
я попробовал ответ Стива, и он работает, но я думаю, что это медленно для больших изображений, потому что он дублирует все изображение.
вы можете установить свойства непосредственно на 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: можно найти здесь:
Я создал категорию MSMutableDictionary, чтобы помочь сохранить геотег и другие метаданные в изображение. Проверьте мой пост в блоге здесь:
http://blog.codecropper.com/2011/05/adding-metadata-to-ios-images-the-easy-way/
часть включает в себя создание словаря метаданных GPS для данных EXIF. Вот категория CLLocation, чтобы сделать именно это:
поскольку я все еще учусь, мне потребовалось некоторое время, чтобы собрать вместе ответы Стива и Марти, чтобы добавить метаданные к данным для изображения и впоследствии сохранить его. Ниже я сделал быструю реализацию своих ответов, которая не использует методы ImageIO.
учитывая образец буфера CMSampleBuffer
buffer
некоторые CLLocationlocation
, и используя Морти предложение использовать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 }
на этом этапе вы можете записать данные изображения в файл или использовать API фотографий для сохранения изображения в рулоне камеры (используя
PHAssetCreationRequest's addResource:with:data:options
для iOS 9+ или для более старых версий iOS путем записи imageData во временный файл и последующего вызоваPHAssetChangeRequest.creationRequestForAssetFromImage:atFileURL
). Обратите внимание, что ALAssertLibrary является устаревшим для iOS 9. Я предоставляю более подробную информацию о реализации в ответе здесь.
[пересмотр этого ответа из-за downvotes без объяснений.]
Apple обновила свою статью, посвященную этой проблеме (технические вопросы и ответы 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
таким образом, я установил данные 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);