【问题标题】:Write UIImage along with metadata (EXIF, GPS, TIFF) in iPhone's Photo library在 iPhone 的照片库中写入 UIImage 和元数据(EXIF、GPS、TIFF)
【发布时间】:2011-12-19 09:37:13
【问题描述】:

我正在开发一个项目,其中的要求是: - 用户将通过应用程序打开相机 - 捕获图像后,一些数据将附加到捕获图像的元数据中。 我浏览了一些论坛。我试图编写这个逻辑。我想,我已经达到了这一点,但是由于我无法看到我附加到图像的元数据,所以缺少一些东西。 我的代码是:

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

请帮助我做到这一点,并得到一些积极的东西。 看看最后一行,我是否保存了包含元数据的图像? 那时图像正在保存,但我附加到它的元数据没有保存。

提前致谢。

【问题讨论】:

  • 你找到答案了吗?

标签: iphone objective-c exif alassetslibrary


【解决方案1】:

Apple 已更新其文章以解决此问题(技术问答 QA1622)。如果您使用的是旧版本的 Xcode,您可能仍然有文章说,或多或少,运气不好,如果不对图像数据进行低级解析,您将无法做到这一点。

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

我对那里的代码进行了如下修改:

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

我是从

imagePickerController:didFinishPickingMediaWithInfo:

这是图像选择器的委托方法。

我使用辅助方法(改编自 GusUtils)从某个位置构建 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];

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

}

到目前为止,这对我来说在 iOS4 和 iOS5 设备上运行良好。

更新:和 iOS6/iOS7 设备。我使用以下代码构建了一个简单的项目:

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

【讨论】:

    【解决方案2】:

    函数:UIImageWriteToSavePhotosAlbum只写入图像数据。

    您需要阅读ALAssetsLibrary

    你最终要调用的方法是:

     ALAssetsLibrary *library = [[ALAssetsLibrary alloc]
     [library writeImageToSavedPhotosAlbum:metadata:completionBlock];
    

    【讨论】:

    • ALAssetsLibrary 在 iOS9 中已弃用。 PhotoKit 就是现在的位置。
    【解决方案3】:

    对于任何来这里尝试在您的应用中使用相机拍照并将图像文件与 GPS 元数据一起保存到相机胶卷的人,我有一个使用 Photos APISwift 解决方案因为 ALAssetsLibrary 自 iOS 9.0 起已弃用

    正如 rickster 在此 answer 中提到的,即使您设置了新资产的 .location 属性,Photos API不会将位置数据直接嵌入到 JPG 图像文件中。

    给定一个CMSampleBuffer样本缓冲区buffer,一些CLLocationlocation,并使用Morty的suggestion来使用CMSetAttachments以避免重复图像,我们可以执行以下操作。扩展CLLocation的gpsMetadata方法可以在here找到。

    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)
    

    savePhoto 方法如下。请注意,方便的 addResource:with:data:options 方法仅在 iOS 9 中可用。如果您支持早期的 iO​​S 并希望使用 Photos API,那么您必须创建一个临时文件,然后从该 URL 的文件创建一个资产,如果您希望正确嵌入 GPS 元数据 (PHAssetChangeRequest.creationRequestForAssetFromImage:atFileURL)。仅设置 PHAsset 的 .location 不会将您的新元数据嵌入到实际文件本身中。

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

    除此之外: 如果您只是想将带有 GPS 元数据的图像保存到临时文件或文档中(而不是相机胶卷/照片库),您可以跳过使用 Photos API 并直接将 imageData 写入 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
    }
    

    【讨论】:

    • 这很好,但是如何从 UIImage 中获取 CMSampleBuffer?
    • @Chris 在我的例子中,我为AVCaptureStillImageOutput 设置了一个变量,我们可以从中获得一个 AVCaptureConnection (.connection(withMediaType:)。然后captureStillImageAsynchronously(from:) 的完成块有一个CMSampleBuffer? 和一个错误。查看stackoverflow.com/questions/39492339/swift-3-photo-capturing 获取一些示例代码。
    • location 参数似乎没有在 savePhoto 函数中使用。
    • 这很好,但它没有回答问题。我们不是从一个样本缓冲区开始,而是从一个刚刚拍照的 UIImagePickerController 开始。
    • @matt 我建议使用AVFoundation,因为它非常适合复杂的文件处理并允许更多的相机自定义。 AVCaptureStillImageOutputcaptureStillImageAsynchronously 生成样本缓冲区。 Herehere 列出了 AVFoundation 相对于 UIImagePickerController 的优势。 OP 给出的两个要求都没有明确声明必须使用UIImagePickerController
    【解决方案4】:

    其中一部分涉及生成 GPS 元数据。这是 CLLocation 上的一个类别,可以做到这一点:

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

    【讨论】:

    • 我不小心投了反对票!对不起!这是一个很好的要点。
    【解决方案5】:

    在应用程序中从摄像头捕获的图像中获取元数据:

    UIImage *pTakenImage= [info objectForKey:@"UIImagePickerControllerOriginalImage"];
    
    NSMutableDictionary *imageMetadata = [[NSMutableDictionary alloc] initWithDictionary:[info objectForKey:UIImagePickerControllerMediaMetadata]];
    

    现在使用提取的元数据将图像保存到库中:

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

    或者想保存到本地目录

    CGImageDestinationAddImageFromSource(destinationPath,sourceImage,0, (CFDictionaryRef)imageMetadata);
    

    【讨论】:

    • PHPhotoLibrary 的解决方案是什么,因为 ALAssetsLibrary 自 iOS 9.0 以来已被弃用。
    【解决方案6】:

    我们试图解决的问题是:用户刚刚用 UIImagePickerController 相机拍照。我们得到的是一个 UIImage。既然我们没有 AssetsLibrary 框架,我们如何在将元数据保存到相机胶卷(照片库)时将元数据折叠到 UIImage 中?

    答案(据我所知)是:使用 ImageIO 框架。从 UIImage 中提取 JPEG 数据,将其用作源并将其和元数据字典写入目标,并将目标数据作为 PHAsset 保存到相机胶卷中。

    在本例中,im 是 UIImage,meta 是元数据字典:

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

    一个很好的测试方法——也是一个常见的用例——是通过 UIImagePickerControllerMediaMetadata 键从 UIImagePickerController 委托 info 字典接收照片元数据,并将其折叠到 PHAsset 中,同时将其保存到照片库中。

    【讨论】:

    【解决方案7】:

    有许多处理图像和元数据的框架。

    Assets Framework 已弃用,取而代之的是 Photos Library 框架。如果你实现了AVCapturePhotoCaptureDelegate 来拍摄照片,你可以这样做:

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

    元数据是字典的字典,你必须参考CGImageProperties

    我写过这个话题here

    【讨论】:

      【解决方案8】:

      这是@matt 答案的细微变化。

      以下代码仅使用一个 CGImageDestination 并且更有趣的是允许在 iOS11+ 上以 HEIC 格式保存。

      请注意,压缩质量会在添加图像之前添加到元数据中。 0.8大致是原生相机保存的压缩质量。

      //img is the UIImage and metadata the metadata received from the picker
      NSMutableDictionary *meta_plus = metadata.mutableCopy;
      //with CGimage, one can set compression quality in metadata
      meta_plus[(NSString *)kCGImageDestinationLossyCompressionQuality] = @(0.8);
      NSMutableData *img_data = [NSMutableData new];
      NSString *type;
      if (@available(iOS 11.0, *)) type = AVFileTypeHEIC;
      else type = @"public.jpeg";
      CGImageDestinationRef dest = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)img_data, (__bridge CFStringRef)type, 1, nil);
      CGImageDestinationAddImage(dest, img.CGImage, (__bridge CFDictionaryRef)meta_plus);
      CGImageDestinationFinalize(dest);
      CFRelease(dest); //image is in img_data
      //go for the PHLibrary change request
      

      【讨论】:

        猜你喜欢
        • 2016-02-21
        • 1970-01-01
        • 1970-01-01
        • 2013-06-26
        • 2010-11-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多