web-dev-qa-db-fra.com

Problème de configuration des données exif pour une image

J'utilise le nouveau framework ImageIO dans iOS 4.1. Je récupère avec succès les métadonnées exif en utilisant les éléments suivants:

CFDictionaryRef metadataDict = CMGetAttachment(sampleBuffer, kCGImagePropertyExifDictionary , NULL);

En le lisant, il semble valide. L'enregistrement d'une image sur fonctionne, mais il n'y a jamais aucune donnée exif dans l'image.

    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);
53
akaru

Le blog suivant est l'endroit où j'ai obtenu ma réponse lorsque j'ai eu des problèmes avec la modification et l'enregistrement de cacao caféiné Exif Data. Cela fonctionne sur iOS.

Voici mon code de test pour écrire des données Exif et GPS. C'est à peu près un copier-coller du code du blog ci-dessus. J'utilise ceci pour écrire des données exif sur une image capturée.

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);
79
Steve McFarlin

J'ai essayé la réponse de Steve et cela a fonctionné, mais je pense que c'est lent pour les grandes images car cela duplique toute l'image 

Vous pouvez définir les propriétés directement sur CMSampleBuffer à l'aide de CMSetAttachments. Faites ceci avant d'appeler 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);

Et la méthode getGPSDictionaryForLocation: peut être trouvée ici: 

Enregistrement des informations de géolocalisation avec une photo sur iOS4.1

14
Morty

J'ai créé une catégorie MSMutableDictionary pour aider à enregistrer la géotag et d'autres métadonnées dans une image. Découvrez mon blog ici:

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

3
Gustavo Ambrozio

Un élément implique la création du dictionnaire de métadonnées GPS pour les données EXIF. Voici une catégorie de CLLocation pour faire exactement cela:

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

1
Philip

Étant donné que j'apprends encore, il m'a fallu un certain temps pour rassembler les réponses de Steve et de Marty afin d'ajouter des métadonnées aux données d'une image, puis de les sauvegarder. Ci-dessous, j'ai réalisé une implémentation Swift de leurs réponses qui n'utilise pas les méthodes ImageIO.

A partir d'un exemple de tampon CMSampleBuffer buffer, de CLLocation location et de l'utilisation de suggestion de Morty pour utiliser CMSetAttachments afin d'éviter de dupliquer l'image, nous pouvons procéder comme suit. La méthode gpsMetadata qui étend CLLocation peut être trouvée ici (également 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
}

À ce stade, vous pouvez écrire les données d'image dans un fichier ou utiliser l'API Photos pour enregistrer l'image sur la pellicule (à l'aide de PHAssetCreationRequest's addResource:with:data:options pour iOS 9 ou version ultérieure, ou pour les versions iOS plus anciennes en écrivant l'imageData dans un fichier temporaire. fichier puis en appelant PHAssetChangeRequest.creationRequestForAssetFromImage:atFileURL). Notez que ALAssertLibrary est obsolète pour iOS 9. Je fournis plus de détails sur l'implémentation dans une réponse ici .

1
Undrea

[Revoir cette réponse à cause de votes négatifs sans explications.]

Apple a mis à jour son article traitant de ce problème (QA1622, questions techniques). Si vous utilisez une ancienne version de Xcode, vous pouvez toujours avoir l'article qui dit, plus ou moins, pas de chance, vous ne pouvez pas le faire sans une analyse de bas niveau des données d'image.

La version mise à jour est ici:

https://developer.Apple.com/library/ios/#qa/qa1622/_index.html

J'ai adapté le code comme suit:

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

et j'appelle cela de

imagePickerController:didFinishPickingMediaWithInfo:

qui est la méthode déléguée pour le sélecteur d'images. (C'est là que j'ai mis la logique de voir s'il y avait une image à sauvegarder, etc.)

Pour être complet, voici la méthode d'assistance permettant d'obtenir les données GPS sous forme de dictionnaire:

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

}

Voir aussi Ecrivez UIImage avec les métadonnées (EXIF, GPS, TIFF) dans la photothèque de l'iPhone

0
Code Roadie

De cette façon, je définis les données EXIF, vous pouvez également compresser une photo si besoin, cela a résolu le problème pour moi: espérons

// 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);
0
CGR