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

je développe un projet, où les exigences sont: - L'utilisateur ouvrira la caméra à travers l'application - Lors de la capture d'une Image, certaines données seront ajoutées aux métadonnées de l'image capturée. Je suis passé par quelques-uns des forums. J'ai essayé de code de cette logique. Je suppose que j'en suis arrivé là, mais il manque quelque chose, car je ne suis pas en mesure de voir les métadonnées que je joins à l'image. Mon code est:

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

Aidez-moi à faire et pour obtenir quelque chose de positif. Regardez la dernière ligne, je suis l'enregistrement de l'image avec mon métadonnées? L'image est sauvegardée à ce moment-là, mais les métadonnées que j'y ajoute ne sont pas sauvegardées.

Merci d'avance.

14
demandé sur Efren 2011-11-01 14:44:48

7 réponses

la fonction: UIImageWriteToSavePhotosAlbum n'écrit que les données de l'image.

vous devez lire sur le ALAssetsLibrary

la méthode que vous voulez finalement appeler est:

 ALAssetsLibrary *library = [[ALAssetsLibrary alloc]
 [library writeImageToSavedPhotosAlbum:metadata:completionBlock];
7
répondu Rayfleck 2017-10-10 07:11:56

Apple a mis à jour leur article traitant de cette question (Q&A technique QA1622). 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 parsing de bas niveau des données d'image.

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

j'y ai adapté le code comme suit:

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

et j'appelle ce de

imagePickerController:didFinishPickingMediaWithInfo:

qui est la méthode de délégué pour le capteur d'image.

j'utilise une méthode d'aide (adaptée de GusUtils ) pour construire un dictionnaire de métadonnées GPS à partir d'un emplacement:

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

}

Jusqu'à présent cela fonctionne bien pour moi sur les appareils iOS4 et iOS5.

Update : et dispositifs iOS6/iOS7. J'ai construit un projet simple en utilisant ce code:

https://github.com/5teev/MetaPhotoSave

13
répondu Code Roadie 2013-09-13 22:55:04

pour toute personne qui vient ici en essayant de prendre une photo avec l'appareil photo dans votre application et en sauvegardant le fichier image sur le rouleau de l'appareil photo avec des métadonnées GPS, j'ai un solution Swift qui utilise le API Photos depuis Bibliothèque Alassetsli est déprécié à partir de iOS 9.0 .

comme mentionné par rickster sur ce réponse , L'API Photos ne not embed les données de localisation directement dans un fichier image JPG même si vous définissez le .emplacement propriété du nouvel actif.

avec un tampon D'échantillon CMSampleBuffer buffer , quelques Cllocations location , et en utilisant la suggestion de Morty pour utiliser CMSetAttachments afin d'éviter la duplication de l'image, nous pouvons faire ce qui suit. La méthode gpsMetadata étendant CLLocation peut être trouvée ici .

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)

la méthode savePhoto est décrite ci-dessous. Notez que la méthode pratique addResource:with:data:options n'est disponible qu'en iOS 9. Si vous supportez un iOS antérieur et que vous voulez utiliser L'API Photos, alors vous devez créer un fichier temporaire puis créer un actif à partir du fichier à cette URL si vous voulez avoir les métadonnées GPS correctement intégrées ( PHAssetChangeRequest.creationRequestForAssetFromImage:atFileURL ). Seule la mise en PHAsset .l'emplacement N'intégrera pas vos nouvelles métadonnées dans le fichier lui-même.

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

mis à part: Si vous voulez simplement sauvegarder l'image avec des métadonnées GPS dans vos fichiers ou documents temporaires (par opposition à la photothèque), vous pouvez sauter en utilisant l'API Photos et écrire directement l'imageData à une 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
}
4
répondu Undrea 2017-10-10 15:30:17

une partie de ceci implique la génération des métadonnées GPS. Voici une catégorie sur CLLocation pour faire exactement cela:

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

2
répondu Philip 2013-07-20 01:46:08

Obtenir des méta-données à partir de cam image capturée au sein d'une application:

UIImage *pTakenImage= [info objectForKey:@"UIImagePickerControllerOriginalImage"];

NSMutableDictionary *imageMetadata = [[NSMutableDictionary alloc] initWithDictionary:[info objectForKey:UIImagePickerControllerMediaMetadata]];

maintenant pour sauvegarder l'image dans la bibliothèque avec les métadonnées extraites:

ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init];
[library writeImageToSavedPhotosAlbum:[sourceImage CGImage] metadata:imageMetadata completionBlock:Nil];
[library release];

ou voulez sauvegarder dans le répertoire local

CGImageDestinationAddImageFromSource(destinationPath,sourceImage,0, (CFDictionaryRef)imageMetadata);
2
répondu Usman Nisar 2013-11-25 06:54:01

le problème que nous essayons de résoudre est que l'utilisateur vient de prendre une photo avec l'appareil photo UIImagePickerController. Ce que nous obtenons est une UIImage. Comment plier des métadonnées dans que UIImage comme nous l'enregistrer dans la pellicule (photothèque), maintenant que nous n'avons pas les AssetsLibrary cadre?

la réponse (pour autant que je sache) est: utilisez le cadre ImageIO. Extraire les données JPEG de L'UIImage, les utiliser comme source et les écrire ainsi que le dictionnaire de métadonnées dans la destination, et enregistrer les données de destination comme un PHAsset dans le rouleau de caméra.

dans cet exemple, im est L'UIImage et meta est le dictionnaire de métadonnées:

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

une bonne façon de tester - et un cas d'utilisation commune - est de recevoir les métadonnées photo de l'UIImagePickerController délégué info dictionnaire à travers le UIImagePickerControllerMediaMetadata clé et le plier dans le châssis que nous enregistrons dans la photothèque.

1
répondu matt 2017-10-23 16:17:31

il existe de nombreux cadres qui traitent de l'image et des métadonnées.

Actifs Cadre est obsolète et remplacé par une Bibliothèque de Photos-cadre. Si vous avez implémenté AVCapturePhotoCaptureDelegate pour capturer des photos, vous pouvez le faire:

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)
    ...
}

Les métadonnées est un dictionnaire de dictionnaires, et vous devez vous référer à CGImageProperties .

j'ai écrit sur ce sujet ici .

0
répondu samwize 2018-05-18 03:17:34