Проблема с настройкой exif-данных для изображения - PullRequest
54 голосов
/ 26 февраля 2011

Я использую новый каркас 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);

Ответы [ 8 ]

80 голосов
/ 14 марта 2011

В следующем посте я получил ответ, когда у меня возникли проблемы с изменением и сохранением данных 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);
14 голосов
/ 11 мая 2011

Я попробовал ответ Стива, и он работает, но я думаю, что это медленно для больших изображений, потому что он дублирует все изображение.

Вы можете установить свойства непосредственно в 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 голосов
/ 15 мая 2011

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

http://blog.codecropper.com/2011/05/adding-metadata-to-ios-images-the-easy-way/

1 голос
/ 17 июня 2019

SWIFT 5:

Мне потребовалась неделя на то, чтобы собрать все части в рабочий код.

Это сохранит временный файл UIImage в 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) в альбом фотографий, или сохранить его в виде файла, или поделиться им с другими приложениями, или с чем угодно.

1 голос
/ 12 апреля 2017

Поскольку я все еще учусь, мне потребовалось некоторое время, чтобы собрать воедино ответы Стива и Марти, чтобы добавить метаданные в Данные для изображения и затем сохранить его. Ниже я сделал Swift-реализацию их ответов, в которой не используются методы ImageIO.

С учетом буфера семплов CMSampleBuffer buffer, некоторого CLLocation location и использования предложения Morty для использования 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 Photos для сохранения изображения в Camera Roll (используя PHAssetCreationRequest's addResource:with:data:options для iOS 9+ или для более старых версий iOS, записав imageData в временный файл с последующим вызовом PHAssetChangeRequest.creationRequestForAssetFromImage:atFileURL). Обратите внимание, что ALAssertLibrary устарела для iOS 9. Я предоставляю больше деталей реализации в ответе здесь .

1 голос
/ 20 июля 2013

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

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

0 голосов
/ 28 ноября 2017

Таким образом, я устанавливаю данные EXIF, также вы можете сжать фотографию, если это необходимо, это решило проблему для меня: надеюсь, это поможет

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

[Пересмотр этого ответа из-за отрицательных голосов без объяснений.]

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

...