在 iPhone 的照片库中写入 UIImage 以及元数据(EXIF、GPS、TIFF)

发布于 2024-12-13 07:55:12 字数 3546 浏览 2 评论 0 原文

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

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

请帮助我做到这一点并得到一些积极的东西。 看最后一行,我是否保存了带有元数据的图像? 此时图像已保存,但我附加到图像的元数据并未保存。

提前致谢。

I am developing a project, where the requirements are:
- User will open the camera through the application
- Upon capturing an Image, some data will be appended to the captured image's metadata.
I have gone through some of the forums. I tried to code this logic. I guess, I have reached to the point, but something is missing as I am not able to see the metadata that I am appending to the image.
My code is:

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

Kindly, help me to do this and get something positive.
Look at the last line, am I saving the image with my metadata in it?
The image is getting saved at that point, but the metadata that I am appending to it, is not getting saved.

Thanks in advance.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

谈下烟灰 2024-12-20 07:55:12

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

Apple has updated their article addressing this issue (Technical Q&A QA1622). If you're using an older version of Xcode, you may still have the article that says, more or less, tough luck, you can't do this without low-level parsing of the image data.

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

I adapted the code there as follows:

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

and I call this from

imagePickerController:didFinishPickingMediaWithInfo:

which is the delegate method for the image picker.

I use a helper method (adapted from GusUtils) to build a GPS metadata dictionary from a location:

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

}

So far this is working well for me on iOS4 and iOS5 devices.

Update: and iOS6/iOS7 devices. I built a simple project using this code:

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

七七 2024-12-20 07:55:12

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

您需要阅读 ALAssetsLibrary

你最终要调用的方法是:

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

The function: UIImageWriteToSavePhotosAlbum only writes the image data.

You need to read up on the ALAssetsLibrary

The method you ultimately want to call is:

 ALAssetsLibrary *library = [[ALAssetsLibrary alloc]
 [library writeImageToSavedPhotosAlbum:metadata:completionBlock];
似狗非友 2024-12-20 07:55:12

对于任何来这里尝试使用应用中的相机拍照并将图像文件与 GPS 元数据保存到相机胶卷的人,我有一个使用 照片 APIALAssetsLibrary 自 iOS 9.0 起已弃用。

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

给定 CMSampleBuffer 示例缓冲区 buffer、一些 CLLocation location,并使用 Morty 的建议< /a> 使用CMSetAttachments 为了避免重复图像,我们可以执行以下操作。扩展 CLLocation 的 gpsMetadata 方法可以在此处找到。

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,则必须创建一个临时文件,然后如果您希望正确嵌入 GPS 元数据 (PHAssetChangeRequest.creationRequestForAssetFromImage:atFileURL),请从该 URL 处的文件创建资产。仅设置 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
}

For anyone who comes here trying to take a photo with the camera in your app and saving the image file to the camera roll with GPS metadata, I have a Swift solution that uses the Photos API since ALAssetsLibrary is deprecated as of iOS 9.0.

As mentioned by rickster on this answer, the Photos API does not embed location data directly into a JPG image file even if you set the .location property of the new asset.

Given a CMSampleBuffer sample buffer buffer, some CLLocation location, and using Morty’s suggestion to use CMSetAttachments in order to avoid duplicating the image, we can do the following. The gpsMetadata method extending CLLocation can be found 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)

The savePhoto method is below. Note that the handy addResource:with:data:options method is available only in iOS 9. If you are supporting an earlier iOS and want to use the Photos API, then you must make a temporary file and then create an asset from the file at that URL if you want to have the GPS metadata properly embedded (PHAssetChangeRequest.creationRequestForAssetFromImage:atFileURL). Only setting PHAsset’s .location will NOT embed your new metadata into the actual file itself.

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

Aside:
If you are just wanting to save the image with GPS metadata to your temporary files or documents (as opposed to the camera roll/photo library), you can skip using the Photos API and directly write the imageData to a 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
}
┾廆蒐ゝ 2024-12-20 07:55:12

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

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

Getting meta data from cam captured image within an application:

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

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

now to save image to library with extracted metadata:

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

or want to save to local directory

CGImageDestinationAddImageFromSource(destinationPath,sourceImage,0, (CFDictionaryRef)imageMetadata);
怎樣才叫好 2024-12-20 07:55:12

我们要解决的问题是:用户刚刚用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)
})

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

The problem we are trying to solve is: the user has just taken a picture with the UIImagePickerController camera. What we get is a UIImage. How do we fold metadata into that UIImage as we save it into the camera roll (photo library), now that we don't have the AssetsLibrary framework?

The answer (as far as I can make out) is: use the ImageIO framework. Extract the JPEG data from the UIImage, use it as a source and write it and the metadata dictionary into the destination, and save the destination data as a PHAsset into the camera roll.

In this example, im is the UIImage and meta is the metadata dictionary:

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

A good way to test — and a common use case — is to receive the photo metadata from the UIImagePickerController delegate info dictionary thru the UIImagePickerControllerMediaMetadata key and fold it into the PHAsset as we save it into the photo library.

赴月观长安 2024-12-20 07:55:12

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

资产框架已弃用,并由照片库框架取代。如果你实现了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

我在此处写了有关此主题的文章。

There are many frameworks that deals with image and metadata.

Assets Framework is deprecated, and replaced by Photos Library framework. If you implemented AVCapturePhotoCaptureDelegate to capture photos, you can do so:

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

The metadata is a dictionary of dictionaries, and you have to refer to CGImageProperties.

I wrote about this topic here.

唯憾梦倾城 2024-12-20 07:55:12

这是 @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

Here is a slight variation of @matt answer.

The following code use only one CGImageDestination and more interesting allow to save in HEIC format on iOS11+.

Notice that the compression quality is added to the metadata before adding the image. 0.8 is roughly the compression quality of native camera save.

//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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文