2012-01-25 17 views
23

Soy consciente de cómo guardar metadatos usando ALAssets. Pero, quiero guardar una imagen, o subirla a alguna parte, con exif intacto. Tengo datos exif como NSDictionary. ¿Pero cómo puedo inyectarlo correctamente en un UIImage (o probablemente una representación NSData JPEG)?Cómo escribir metadatos exif en una imagen (no en el rollo de la cámara, solo un UIImage o JPEG)

+0

[Esta respuesta] (http://stackoverflow.com/questions/1238838/uiimagepickercontroller-and-extracting-exif-data-from-existing-photos) podría ser útil para usted. – user1118321

+0

Pruebe libexif: http://libexif.sourceforge.net/docs.html –

Respuesta

14

UIImage no contiene información de metadatos (está desprotegida). Así que si usted desea guardarla sin utilizar el método imagepicker (no en el rollo de la cámara):

Siga la respuesta aquí para escribir en un archivo con los metadatos intactos:

Problem setting exif data for an image

idea de por qué lo haría esto se downvoted pero aquí es el método:

en este caso im conseguir la imagen a través AVFoundation y esto es lo que pasa en el

[[self stillImageOutput] captureStillImageAsynchronouslyFromConnection:videoConnection 
                completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error) 
{ 
    // code here 
} 

bloque de código:

CFDictionaryRef metaDict = CMCopyDictionaryOfAttachments(NULL, imageSampleBuffer, kCMAttachmentMode_ShouldPropagate); 

    CFMutableDictionaryRef mutable = CFDictionaryCreateMutableCopy(NULL, 0, metaDict); 

    // Create formatted date 
    NSTimeZone  *timeZone = [NSTimeZone timeZoneWithName:@"UTC"]; 
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 
    [formatter setTimeZone:timeZone]; 
    [formatter setDateFormat:@"HH:mm:ss.SS"]; 

    // Create GPS Dictionary 
    NSDictionary *gpsDict = [NSDictionary dictionaryWithObjectsAndKeys: 
           [NSNumber numberWithFloat:fabs(loc.coordinate.latitude)], kCGImagePropertyGPSLatitude 
           , ((loc.coordinate.latitude >= 0) ? @"N" : @"S"), kCGImagePropertyGPSLatitudeRef 
           , [NSNumber numberWithFloat:fabs(loc.coordinate.longitude)], kCGImagePropertyGPSLongitude 
           , ((loc.coordinate.longitude >= 0) ? @"E" : @"W"), kCGImagePropertyGPSLongitudeRef 
           , [formatter stringFromDate:[loc timestamp]], kCGImagePropertyGPSTimeStamp 
           , [NSNumber numberWithFloat:fabs(loc.altitude)], kCGImagePropertyGPSAltitude 
           , nil]; 

    // The gps info goes into the gps metadata part 

    CFDictionarySetValue(mutable, kCGImagePropertyGPSDictionary, (__bridge void *)gpsDict); 

    // Here just as an example im adding the attitude matrix in the exif comment metadata 

    CMRotationMatrix m = att.rotationMatrix; 
    GLKMatrix4 attMat = GLKMatrix4Make(m.m11, m.m12, m.m13, 0, m.m21, m.m22, m.m23, 0, m.m31, m.m32, m.m33, 0, 0, 0, 0, 1); 

    NSMutableDictionary *EXIFDictionary = (__bridge NSMutableDictionary*)CFDictionaryGetValue(mutable, kCGImagePropertyExifDictionary); 

    [EXIFDictionary setValue:NSStringFromGLKMatrix4(attMat) forKey:(NSString *)kCGImagePropertyExifUserComment]; 

    CFDictionarySetValue(mutable, kCGImagePropertyExifDictionary, (__bridge void *)EXIFDictionary); 

    NSData *jpeg = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer] ; 

Después de este código que tendrá su imagen en el NSData jpeg y el diccionario correspoding de esa imagen en el cfdictionary mutable.

Todo lo que tiene que hacer ahora es:

CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)jpeg, NULL); 

    CFStringRef UTI = CGImageSourceGetType(source); //this is the type of image (e.g., public.jpeg) 

    NSMutableData *dest_data = [NSMutableData data]; 


    CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge 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) mutable); 

    //tell the destination to write the image data and metadata into our data object. 
    //It will return false if something goes wrong 
    BOOL 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 

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
    NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents folder 
    NSString *dataPath = [documentsDirectory stringByAppendingPathComponent:@"ImagesFolder"]; 

    NSError *error; 
    if (![[NSFileManager defaultManager] fileExistsAtPath:dataPath]) 
     [[NSFileManager defaultManager] createDirectoryAtPath:dataPath withIntermediateDirectories:NO attributes:nil error:&error]; //Create folder 

    // NSString *imageName = @"ImageName"; 

    NSString *fullPath = [dataPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.jpg", name]]; //add our image to the path 

    [dest_data writeToFile:fullPath atomically:YES]; 

    //cleanup 

    CFRelease(destination); 
    CFRelease(source); 

Nota cómo no estoy ahorrando el uso de los ALAssets sino directamente en una carpeta de mi elección.

Por cierto, la mayoría de este código se puede encontrar en el enlace que publiqué al principio.

+0

Gracias. ¿Pero has hecho alguna optimización para la memoria? Recibo advertencias de memoria debido a la duplicación de los datos de imagen en su método. Está el jpeg nsdata, luego un destino. Vi en su respuesta vinculada una forma de no duplicar, pero estaba usando almacenamientos intermedios de muestra, que no estoy usando para las fotos tomadas a través de UIImagePickerController. – akaru

+0

¿Quiere decir que está utilizando el método predeterminado para tomar fotografías? el que guarda automáticamente en el rollo de la cámara? Pensé que uno guardó la imagen con los metadatos incluidos. Si no, el ejemplo de la manzana sí. – Pochi

+0

Estoy usando el selector, pero como una cámara. En este caso, obtengo un UIImage directamente, junto con algunos metadatos. Luego tengo que inyectar datos GPS, etc. Aún tengo que guardarlo manualmente en otra carpeta. – akaru

15

Estoy usando UIImagePickerController para obtener la imagen de la cámara y mi flujo es un poco diferente al descrito por Chiquis. Aquí está:

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { 
    UIImage *image = info[@"UIImagePickerControllerOriginalImage"]; 
    NSString *fullPhotoFilename = ...; // generate the photo name and path here 
    NSData *photoData = [UIImage taggedImageData:image.jpegData metadata:info[@"UIImagePickerControllerMediaMetadata"] orientation:image.imageOrientation]; 
    [photoData writeToFile:fullPhotoFilename atomically:YES]; 
} 

Y el uso de una categoría UIImage poner combinar los datos de imagen con sus metadatos:

#import <ImageIO/ImageIO.h> 
#import "UIImage+Tagging.h" 
#import "LocationHelper.h" 

@implementation UIImage (Tagging) 

+ (NSData *)writeMetadataIntoImageData:(NSData *)imageData metadata:(NSMutableDictionary *)metadata { 
    // create an imagesourceref 
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef) imageData, NULL); 

    // this is the type of image (e.g., public.jpeg) 
    CFStringRef UTI = CGImageSourceGetType(source); 

    // create a new data object and write the new image into it 
    NSMutableData *dest_data = [NSMutableData data]; 
    CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)dest_data, UTI, 1, NULL); 
    if (!destination) { 
     NSLog(@"Error: 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, (__bridge CFDictionaryRef) metadata); 
    BOOL success = NO; 
    success = CGImageDestinationFinalize(destination); 
    if (!success) { 
     NSLog(@"Error: Could not create data from image destination"); 
    } 
    CFRelease(destination); 
    CFRelease(source); 
    return dest_data; 
} 

+ (NSData *)taggedImageData:(NSData *)imageData metadata:(NSDictionary *)metadata orientation:(UIImageOrientation)orientation { 
    CLLocationManager *locationManager = [CLLocationManager new]; 
    CLLocation *location = [locationManager location]; 
    NSMutableDictionary *newMetadata = [NSMutableDictionary dictionaryWithDictionary:metadata]; 
    if (!newMetadata[(NSString *)kCGImagePropertyGPSDictionary] && location) { 
     newMetadata[(NSString *)kCGImagePropertyGPSDictionary] = [LocationHelper gpsDictionaryForLocation:location]; 
    } 

    // Reference: http://sylvana.net/jpegcrop/exif_orientation.html 
    int newOrientation; 
    switch (orientation) { 
     case UIImageOrientationUp: 
      newOrientation = 1; 
      break; 

     case UIImageOrientationDown: 
      newOrientation = 3; 
      break; 

     case UIImageOrientationLeft: 
      newOrientation = 8; 
      break; 

     case UIImageOrientationRight: 
      newOrientation = 6; 
      break; 

     case UIImageOrientationUpMirrored: 
      newOrientation = 2; 
      break; 

     case UIImageOrientationDownMirrored: 
      newOrientation = 4; 
      break; 

     case UIImageOrientationLeftMirrored: 
      newOrientation = 5; 
      break; 

     case UIImageOrientationRightMirrored: 
      newOrientation = 7; 
      break; 

     default: 
      newOrientation = -1; 
    } 
    if (newOrientation != -1) { 
     newMetadata[(NSString *)kCGImagePropertyOrientation] = @(newOrientation); 
    } 
    NSData *newImageData = [self writeMetadataIntoImageData:imageData metadata:newMetadata]; 
    return newImageData; 
} 

Y, por último, aquí es el método que estoy utilizando para generar el diccionario de GPS sea necesario:

+ (NSDictionary *)gpsDictionaryForLocation:(CLLocation *)location { 
    NSTimeZone  *timeZone = [NSTimeZone timeZoneWithName:@"UTC"]; 
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 
    [formatter setTimeZone:timeZone]; 
    [formatter setDateFormat:@"HH:mm:ss.SS"]; 

    NSDictionary *gpsDict = @{(NSString *)kCGImagePropertyGPSLatitude: @(fabs(location.coordinate.latitude)), 
          (NSString *)kCGImagePropertyGPSLatitudeRef: ((location.coordinate.latitude >= 0) ? @"N" : @"S"), 
          (NSString *)kCGImagePropertyGPSLongitude: @(fabs(location.coordinate.longitude)), 
          (NSString *)kCGImagePropertyGPSLongitudeRef: ((location.coordinate.longitude >= 0) ? @"E" : @"W"), 
          (NSString *)kCGImagePropertyGPSTimeStamp: [formatter stringFromDate:[location timestamp]], 
          (NSString *)kCGImagePropertyGPSAltitude: @(fabs(location.altitude)), 
          }; 
    return gpsDict; 
} 

Espero que ayude a alguien. Gracias a Gustavo Ambrozio, Chiquis y otros miembros de SO, pude juntarlo y usarlo en mi proyecto.

+0

Excelente trabajo, esta es la única solución de trabajo que funciona muy bien con ALAssets. – Michael

+0

Gracias. Además de establecer la hora, también tuve que establecer la fecha. Esto solo requiere un formateador de fecha UTC con el formato "aaaa: MM: dd" y una clave de kCGImagePropertyGPSDateStamp. – Ian

4

Hay una manera más fácil.Si necesita ahorrar algo de Exif, puede utilizar SimpleExif pod

En primer lugar crear un ExifContainer:

ExifContainer *container = [[ExifContainer alloc] init]; 

y rellenarlo con todos los datos requred:

[container addUserComment:@"A long time ago, in a galaxy far, far away"]; 
[container addCreationDate:[NSDate dateWithTimeIntervalSinceNow:-10000000]]; 
[container addLocation:locations[0]]; 

continuación, puede agregar estos datos a imagen:

NSData *imageData = [[UIImage imageNamed:@"DemoImage"] addExif:container]; 

Luego solo guarde esta información como un archivo JPEG

0

Estos son los conceptos básicos para configurar los metadatos de Crear y modelar en un archivo .jpg en Swift 3 https://gist.github.com/lacyrhoades/09d8a367125b6225df5038aec68ed9e7 Las versiones de nivel superior, como usar el pod ExifContainer, no me funcionaron.

+0

¿Envolvió esto en una extensión o método, o simplemente incluyó el código en sus métodos de guardado? –

0

que enfrentan el mismo problema, ahora puedo subir archivos con los datos EXIF, también puede comprimir foto si lo necesita, esto resuelve el problema para mí:

// Get your image. 
UIImage *loImgPhoto = [self getImageFromAsset:loPHAsset]; 

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

getImageFromAsset método:

-(UIImage *)getImageFromAsset:(PHAsset *)aPHAsset 

{ 
    __block UIImage *limgImageResult; 

    PHImageRequestOptions *lPHImageRequestOptions = [PHImageRequestOptions new]; 
    lPHImageRequestOptions.synchronous = YES; 

    [self.imageManager requestImageForAsset:aPHAsset 
           targetSize:PHImageManagerMaximumSize 
           contentMode:PHImageContentModeDefault//PHImageContentModeAspectFit 
            options:lPHImageRequestOptions 
           resultHandler:^(UIImage *limgImage, NSDictionary *info) { 

            limgImageResult = limgImage; 
           }]; 


    return limgImageResult; 
} 
Cuestiones relacionadas