Как сохранить исходные метаданные фотографий при редактировании PHAssets?

Я редактирую фотографии с помощью PhotoKit, но обнаружил, что это не сохраняет исходные метаданные фотографии. Это происходит даже с приложением SamplePhotosApp, предоставленным Apple, когда они применяют фильтры Sepia или Chrome. Мой вопрос: как вы гарантируете сохранение всех исходных метаданных фотографий?

Я обнаружил, как можно получить метаданные исходного изображения, и мне удалось сохранить эти метаданные в окончательном CIImage файле, который я создаю, но он все равно удаляется при фиксации редактирования. Должна быть проблема в том, как я конвертирую CIImage в CGImage в UIImage в NSData, или как я записываю это на диск.

asset.requestContentEditingInputWithOptions(options) { (input: PHContentEditingInput!, _) -> Void in
    //Get full image
    let url = input.fullSizeImageURL
    let orientation = self.input.fullSizeImageOrientation
    var inputImage = CIImage(contentsOfURL: url)
    inputImage = inputImage.imageByApplyingOrientation(orientation)

    //do some processing on original photo here and create a CGImage...

    //save the original photo's metadata to a new CIImage:
    let originalMetadata = inputImage.properties()
    let newImage = CIImage(CGImage: editedCGImage, options: [kCIImageProperties: originalMetadata])

    println(newImage.properties()) //correctly prints all metadata!

    //commit changes to disk - somewhere after this line the metadata is lost
    let eaglContext = EAGLContext(API: .OpenGLES2)
    let ciContext = CIContext(EAGLContext: eaglContext)
    let outputImageRef = ciContext.createCGImage(newImage, fromRect: newImage.extent())
    let uiImage = UIImage(CGImage: outputImageRef, scale: 1.0, orientation: UIImageOrientation.Up)
    let jpegNSData = UIImageJPEGRepresentation(uiImage, 0.75)

    let contentEditingOutput = PHContentEditingOutput(contentEditingInput: input)
    let success = jpegData.writeToURL(contentEditingOutput.renderedContentURL, options: NSDataWritingOptions.AtomicWrite, error: _)

    PHPhotoLibrary.sharedPhotoLibrary().performChanges({ () -> Void in
        let request = PHAssetChangeRequest(forAsset: asset)
        request.contentEditingOutput = contentEditingOutput
    }, completionHandler: { (success: Bool, error: NSError!) -> Void in
        if success == false { println('failed to commit image edit: \(error)') }
    })
})

Оригинал – обратите внимание на вкладку GPS:
Метаданные исходной фотографии

После редактирования фотографии:
введите здесь описание изображения


person Jordan H    schedule 07.09.2014    source источник


Ответы (2)


Согласно документу Apple для CIImage.properties:

Если объект CIImage является выходом фильтра (или цепочки фильтров), этот метод возвращает метаданные из исходного входного изображения фильтра.

С учетом сказанного, вот как бы я это сделал, если документация неверна.

Вам нужно сохранить свойства самостоятельно. Для этого вам нужно прочитать содержимое URL-адреса как NSData

NSURL *url = @"http://somewebsite.com/path/to/some/image.jpg";
NSData *imageData = [NSData dataWithContentsOfURL:url];
CIImage *image = [CIImage imageWithData:imageData];

// Save off the properties
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef) imageData);
NSDictionary* metadata = (__bridge NSDictionary *) CGImageSourceCopyProperties(imageSource, NULL);

// process your image
// ...
NSData *outputData = // create data from altered image.

// Save the image
CGImageSourceRef outputImageSource = CGImageSourceCreateWithData((__bridge CFDataRef) outputData);
CFMutableDataRef jpegData = CFDataCreateMutable(NULL, 0);
CGImageDestinationRef outputDestination = CGImageDestinationCreateWithData(jpegData, CGImageSourceGetType(outputImageSource), 1, NULL);

// add the image data to the destination
CGImageDestinationAddImageFromSource(jpegData, outputImageSource, 0, (__bridge CFDictionaryRef) metadata);

if (CGImageDestinationFinalize(outputDestination)) {
    NSLog(@"Successful image creation.");
    // process the image rendering, adjustment data creation and finalize the asset edit.
} else {
    NSLog(@"Image creation failed.");
}

Если вам не нужно изменять метаданные, нет необходимости приводить CFDataRef к NSDictionary*, но я сделал это только для полноты картины.

person Bradley M Handy    schedule 22.09.2014
comment
Вероятно, это ошибка, как вы указали, properties всегда должен возвращать метаданные из исходного входного изображения. Я работаю над преобразованием вашего кода в Swift, выглядит многообещающе. - person Jordan H; 22.09.2014
comment
Удалось ли вам портировать его на Swift? - person Bradley M Handy; 25.09.2014
comment
Хм, я нахожу единственный объект в metadata это Filesize = #. - person Jordan H; 28.01.2015
comment
Если вы используете код Obj-C в своем исходном сообщении, попробуйте использовать метод [CIImage imageWithContentsOfURL: ...] без параметра дополнительных параметров. Согласно документации для ключей словаря изображений, если для ключа словаря kCIImageProperties указать нулевое значение, свойства метаданных будут удалены. На основании этой документации версия метода без параметра options может сохранять свойства. Поскольку я никогда не использовал CIImage, это всего лишь догадка. - person Bradley M Handy; 28.01.2015
comment
Кроме того, в зависимости от типа изображения в файле изображения может быть несколько изображений. Возможно, вам придется использовать метод CGImageSourceCopyPropertiesAtIndex для получения метаданных, если вы адаптируете код из моего предложения. - person Bradley M Handy; 28.01.2015
comment
Мне удалось получить метаданные с помощью CGImageSourceCopyPropertiesAtIndex, спасибо! Он может успешно завершиться, но когда я вызываю performChanges для этих asset и contentEditingOutput, он терпит неудачу. Если я удалю метаданные из параметра options, это удастся. Ты знаешь почему? - person Jordan H; 01.02.2015
comment
Я не знаю точно, но мне кажется, что отсутствие adjustmentData установлено на PHContentEditingOutput. С adjustmentData фреймворк не сохранит редактирование. - person Bradley M Handy; 06.02.2015
comment
Я установил adjustmentData для вывода, просто для краткости пропустил его в приведенном выше коде. - person Jordan H; 06.02.2015

Хотя это можно было сделать в Objective-C, как показал Брэдли, это не работало правильно в Swift и приводило к сбою блока performChanges (см. -metadata-to-image-causes-performchanges-request-to-fail">другой вопрос). Это было исправлено в последних выпусках — Swift 1.2 в Xcode 6.3. Чтобы сохранить метаданные, это просто:

let metadata = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as NSDictionary
CGImageDestinationAddImage(destination, cgImage, metadata)

Или для цели-C:

NSMutableDictionary *imageMetadata = [(NSDictionary *) CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL)) mutableCopy];
CGImageDestinationAddImageFromSource(destination, imageSource, 0, (__bridge CFDictionaryRef)(imageMetadata));

РЕДАКТИРОВАТЬ: Хотя это работает в симуляторе iOS, оно не работает на реальном устройстве с кодом Swift.

person Jordan H    schedule 04.05.2015