Cómo escribir metadatos exif en una imagen (no el rollo de la cámara, solo un UIImage o JPEG)

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

UIImage no contiene información de metadatos (está desprovisto). Entonces, si quieres guardarlo sin utilizar el método de captura de imágenes (no en el rollo de la cámara):

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

Problema al configurar datos exif para una imagen

no tengo idea de por qué sería esto downvoted, pero aquí está el método:

En este caso estoy obteniendo la imagen a través de AVFoundation y esto es lo que va en el

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

código de bloque:

  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, tendrá su imagen en jpeg nsdata y el diccionario correspondiente para esa imagen en el mutable cfdictionary.

Todo lo que tienes que hacer ahora es:

  CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)jpeg, NULL); CFStringRef UTI = CGImageSourceGetType(source); //this is the type of image (eg, 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); 

Tenga en cuenta que no estoy guardando usando los ALAsset, sino directamente en una carpeta de mi elección.

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

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 usando una categoría UIImage para poner combine los datos de imagen con sus metadatos:

 #import  #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 (eg, 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 finalmente, aquí está el método que estoy usando para generar el diccionario GPS 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.

Hay una manera más fácil. Si necesita guardar algunos exif, puede usar el pod SimpleExif

Primero crea un ExifContainer:

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

y llenarlo con todos los datos requeridos:

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

Luego puede agregar esta información a la imagen:

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

Luego solo guarde esta información como JPEG

Estos son los aspectos básicos de la configuración de los metadatos de Hacer 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 funcionaron para mí.

Me enfrenté al mismo problema, ahora puedo cargar archivos con datos EXIF, también puedes comprimir una foto si la necesitas, esto me solucionó el problema:

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

Método getImageFromAsset :

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