Напишите UIImage вместе с метаданными (EXIF, GPS, TIFF) в библиотеке фотографий iPhone - PullRequest
15 голосов
/ 01 ноября 2011

Я занимаюсь разработкой проекта, требования которого: - пользователь откроет камеру через приложение - После захвата изображения некоторые данные будут добавлены к метаданным захваченного изображения. Я прошел через некоторые форумы. Я пытался закодировать эту логику. Я думаю, я дошел до сути, но чего-то не хватает, так как я не могу увидеть метаданные, которые я добавляю к изображению. Мой код:

- (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);    
}

Пожалуйста, помогите мне сделать это и получите что-то позитивное. Посмотрите на последнюю строку, сохраняю ли я изображение с метаданными? В этот момент изображение сохраняется, но метаданные, которые я к нему добавляю, не сохраняются.

Заранее спасибо.

Ответы [ 7 ]

13 голосов
/ 14 июня 2012

Apple обновила свою статью, посвященную этой проблеме (Технические вопросы и ответы 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

7 голосов
/ 01 ноября 2011

Функция: UIImageWriteToSavePhotosAlbum записывает только данные изображения.

Вам нужно прочитать о ALAssetsLibrary

В конечном итоге вы хотите вызвать метод:

 ALAssetsLibrary *library = [[ALAssetsLibrary alloc]
 [library writeImageToSavedPhotosAlbum:metadata:completionBlock];
4 голосов
/ 12 апреля 2017

Для тех, кто приезжает сюда, пытаясь сделать снимок с помощью камеры в вашем приложении и сохранить файл изображения в рулон камеры с метаданными GPS, у меня есть Swift решение , которое использует Фотографии API , поскольку ALAssetsLibrary устарело с iOS 9.0 .

Как уже упоминал Рикстер в этом ответе , API фотографий не встраивает данные о местоположении непосредственно в файл изображения JPG, даже если вы задали свойство .location для нового ресурса.

С учетом буфера семплов CMSampleBuffer buffer, некоторого CLLocation location и использования предложения Морти для использования 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 Photos, то вы должны создать временный файл и затем создать ресурс из файла по этому URL, если вы хотите, чтобы метаданные GPS были правильно внедрены (PHAssetChangeRequest.creationRequestForAssetFromImage:atFileURL). Только установка .location PHAsset НЕ будет встраивать ваши новые метаданные в сам файл.

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
}
2 голосов
/ 25 ноября 2013

Получение метаданных из изображения, полученного с помощью камеры, в приложении:

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);
2 голосов
/ 20 июля 2013

Часть этого включает в себя создание метаданных GPS.Вот категория на CLLocation, чтобы сделать это:

https://gist.github.com/phildow/6043486

1 голос
/ 18 мая 2018

Существует множество фреймворков, которые работают с изображениями и метаданными.

Структура активов устарела и заменена структурой библиотеки фотографий.Если вы реализовали AVCapturePhotoCaptureDelegate для захвата фотографий, вы можете сделать это:

func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
    var metadata = photo.metadata
    metadata[kCGImagePropertyGPSDictionary as String] = gpsMetadata
    photoData = photo.fileDataRepresentation(withReplacementMetadata: metadata,
      replacementEmbeddedThumbnailPhotoFormat: photo.embeddedThumbnailPhotoFormat,
      replacementEmbeddedThumbnailPixelBuffer: nil,
      replacementDepthData: photo.depthData)
    ...
}

Метаданные - это словарь словарей, и вы должны ссылаться на CGImageProperties .

я писал на эту тему здесь .

1 голос
/ 23 октября 2017

Проблема, которую мы пытаемся решить, заключается в следующем: пользователь только что сделал снимок с помощью камеры 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)
})

Хороший способ проверить - и распространенный пример использования - это получить метаданные фотографии из делегата UIImagePickerController info словарь через UIImagePickerControllerMediaMetadata и сложите его в PHAsset, сохраняя в библиотеке фотографий.

...