将更新的图像保存到 PhotoKit 时缺少图像元数据
Missing image metadata when saving updated image into PhotoKit
我在更新图像的元数据并将其保存回照片库时遇到问题。一切 都有效 除了更改后的图像元数据丢失了之前存在的条目,而且我在处理图像或执行照片库更改块时没有收到任何错误。此外,在将字典写回图像之前,字典看起来就像是原始字典加上我在调试器中的字典。
我的问题是:
- 我做错了什么会导致重写现有的
带附加数据的属性回来抹去有什么?
- 是否有更好、更规范的方法来做到这一点?更新图像中的某些元数据似乎需要很多技巧。好像
其他人都在做什么。
编辑:
在保存之前所有的 Exif 和 Tiff 值都存在。这是使用以下代码保存到照片后的全部元数据:
["PixelHeight": 2448, "PixelWidth": 3264, "{Exif}": {
ColorSpace = 1;
PixelXDimension = 3264;
PixelYDimension = 2448;}, "Depth": 8, "ProfileName": sRGB IEC61966-2.1, "Orientation": 1, "{TIFF}": {
Orientation = 1;}, "ColorModel": RGB, "{JFIF}": {
DensityUnit = 0;
JFIFVersion = (
1,
0,
1
);
XDensity = 72;
YDensity = 72;}]
代码,全部在Swift 3,测试iOS 10.1
基本工作流程是:
// Get a mutable copy of the existing Exif meta
let mutableMetaData = getMutableMetadataFrom(imageData: data)
// Check to see if it has the {GPS} entry, if it does just exit.
if let _ = mutableMetaData[kCGImagePropertyGPSDictionary as String] {
callback(imageAsset, true, nil)
return
}
// Add the {GPS} tag to the existing metadata
let clLocation = media.location!.asCLLocation()
mutableMetaData[kCGImagePropertyGPSDictionary as String] =
clLocation.asGPSMetaData()
// Attach the new metadata to the existing image
guard let newImageData = attach(metadata: mutableMetaData, toImageData: data) else {
callback(imageAsset, false, nil)
return
}
let editingOptions = PHContentEditingInputRequestOptions()
imageAsset.requestContentEditingInput(with: editingOptions) { editingInput, info in
guard let editingInput = editingInput else { return }
let library = PHPhotoLibrary.shared()
let output = PHContentEditingOutput(contentEditingInput: editingInput)
output.adjustmentData = PHAdjustmentData(formatIdentifier: "Project", formatVersion: "0.1",
data: "Location Adjustment".data(using: .utf8)!)
do {
try newImageData.write(to: output.renderedContentURL, options: [.atomic])
} catch {
callback(imageAsset, false, error)
return
}
library.performChanges({
let changeRequest = PHAssetChangeRequest(for: imageAsset)
changeRequest.location = clLocation
changeRequest.contentEditingOutput = output
}, completionHandler: { success, error in ... ...
工作流的辅助方法是:
func attach(metadata: NSDictionary, toImageData imageData:Data) -> Data? {
guard
let imageDataProvider = CGDataProvider(data: imageData as CFData),
let cgImage = CGImage(jpegDataProviderSource: imageDataProvider, decode: nil,
shouldInterpolate: true, intent: .defaultIntent),
let newImageData = CFDataCreateMutable(nil, 0),
let type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType,
"image/jpg" as CFString, kUTTypeImage),
let destination = CGImageDestinationCreateWithData(newImageData,
(type.takeRetainedValue()), 1, nil) else {
return nil
}
CGImageDestinationAddImage(destination, cgImage, metadata as CFDictionary)
CGImageDestinationFinalize(destination)
guard
let newProvider = CGDataProvider(data: newImageData),
let newCGImage = CGImage(jpegDataProviderSource: newProvider, decode: nil,
shouldInterpolate: false, intent: .defaultIntent) else {
return nil
}
return UIImageJPEGRepresentation(UIImage(cgImage: newCGImage), 1.0)
}
func getMutableMetadataFrom(imageData data : Data) -> NSMutableDictionary {
let imageSourceRef = CGImageSourceCreateWithData(data as CFData, nil)
let currentProperties = CGImageSourceCopyPropertiesAtIndex(imageSourceRef!, 0, nil)
let mutableDict = NSMutableDictionary(dictionary: currentProperties!)
return mutableDict
}
此外 asGPSMetaData
是 CLLocation
的扩展,看起来是 this Gist
的 Swift 3 版本
事实证明,问题根本不是使用 CoreGraphics 处理图像或元数据,而是我忽略的几件事:
- 从包含 EXIF 或 GPS 数据的数据构建 UIImage
删除该数据...实际上,它删除了 most 的元数据
除了一组核心的 JFIF 和大小数据(使用 JPEG 时)。回想起来这是有道理的,因为它们的内部表示只是原始数据。但是,我没有在文档中找到任何关于元数据的明确声明。
- 鉴于先前的观点,将数据(带有元数据的图像)对象放入其中的主要两种方法
照片库要么将其写入临时文件
并阅读它
PHAssetChangeRequest::creationRequestForAssetFromImage(atFileURL:)
或使用 PHAssetCreationRequest::forAsset
创建创建请求
然后使用 PHAssetCreationRequest::addResource(with:data:options:)
将数据添加为照片。我选择了后者,因为它移动较少
部分。
所以我想所有这些都取代了漂亮、简洁的 ALAssetsLibrary::writeImage(toSavedPhotosAlbum:metadata:completionBlock:)
。
照片库的最终更改块最终是这样的:
var assetID: String?
PHPhotoLibrary.shared().performChanges({
let creationRequest = PHAssetCreationRequest.forAsset()
creationRequest.addResource(with: .photo, data: imageWithMetaData as Data, options: nil)
creationRequest.location = clLocation
assetID = creationRequest.placeholderForCreatedAsset?.localIdentifier
}) { success, error in ...
我在更新图像的元数据并将其保存回照片库时遇到问题。一切 都有效 除了更改后的图像元数据丢失了之前存在的条目,而且我在处理图像或执行照片库更改块时没有收到任何错误。此外,在将字典写回图像之前,字典看起来就像是原始字典加上我在调试器中的字典。
我的问题是:
- 我做错了什么会导致重写现有的 带附加数据的属性回来抹去有什么?
- 是否有更好、更规范的方法来做到这一点?更新图像中的某些元数据似乎需要很多技巧。好像 其他人都在做什么。
编辑:
在保存之前所有的 Exif 和 Tiff 值都存在。这是使用以下代码保存到照片后的全部元数据:
["PixelHeight": 2448, "PixelWidth": 3264, "{Exif}": {
ColorSpace = 1;
PixelXDimension = 3264;
PixelYDimension = 2448;}, "Depth": 8, "ProfileName": sRGB IEC61966-2.1, "Orientation": 1, "{TIFF}": {
Orientation = 1;}, "ColorModel": RGB, "{JFIF}": {
DensityUnit = 0;
JFIFVersion = (
1,
0,
1
);
XDensity = 72;
YDensity = 72;}]
代码,全部在Swift 3,测试iOS 10.1
基本工作流程是:
// Get a mutable copy of the existing Exif meta
let mutableMetaData = getMutableMetadataFrom(imageData: data)
// Check to see if it has the {GPS} entry, if it does just exit.
if let _ = mutableMetaData[kCGImagePropertyGPSDictionary as String] {
callback(imageAsset, true, nil)
return
}
// Add the {GPS} tag to the existing metadata
let clLocation = media.location!.asCLLocation()
mutableMetaData[kCGImagePropertyGPSDictionary as String] =
clLocation.asGPSMetaData()
// Attach the new metadata to the existing image
guard let newImageData = attach(metadata: mutableMetaData, toImageData: data) else {
callback(imageAsset, false, nil)
return
}
let editingOptions = PHContentEditingInputRequestOptions()
imageAsset.requestContentEditingInput(with: editingOptions) { editingInput, info in
guard let editingInput = editingInput else { return }
let library = PHPhotoLibrary.shared()
let output = PHContentEditingOutput(contentEditingInput: editingInput)
output.adjustmentData = PHAdjustmentData(formatIdentifier: "Project", formatVersion: "0.1",
data: "Location Adjustment".data(using: .utf8)!)
do {
try newImageData.write(to: output.renderedContentURL, options: [.atomic])
} catch {
callback(imageAsset, false, error)
return
}
library.performChanges({
let changeRequest = PHAssetChangeRequest(for: imageAsset)
changeRequest.location = clLocation
changeRequest.contentEditingOutput = output
}, completionHandler: { success, error in ... ...
工作流的辅助方法是:
func attach(metadata: NSDictionary, toImageData imageData:Data) -> Data? {
guard
let imageDataProvider = CGDataProvider(data: imageData as CFData),
let cgImage = CGImage(jpegDataProviderSource: imageDataProvider, decode: nil,
shouldInterpolate: true, intent: .defaultIntent),
let newImageData = CFDataCreateMutable(nil, 0),
let type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType,
"image/jpg" as CFString, kUTTypeImage),
let destination = CGImageDestinationCreateWithData(newImageData,
(type.takeRetainedValue()), 1, nil) else {
return nil
}
CGImageDestinationAddImage(destination, cgImage, metadata as CFDictionary)
CGImageDestinationFinalize(destination)
guard
let newProvider = CGDataProvider(data: newImageData),
let newCGImage = CGImage(jpegDataProviderSource: newProvider, decode: nil,
shouldInterpolate: false, intent: .defaultIntent) else {
return nil
}
return UIImageJPEGRepresentation(UIImage(cgImage: newCGImage), 1.0)
}
func getMutableMetadataFrom(imageData data : Data) -> NSMutableDictionary {
let imageSourceRef = CGImageSourceCreateWithData(data as CFData, nil)
let currentProperties = CGImageSourceCopyPropertiesAtIndex(imageSourceRef!, 0, nil)
let mutableDict = NSMutableDictionary(dictionary: currentProperties!)
return mutableDict
}
此外 asGPSMetaData
是 CLLocation
的扩展,看起来是 this Gist
事实证明,问题根本不是使用 CoreGraphics 处理图像或元数据,而是我忽略的几件事:
- 从包含 EXIF 或 GPS 数据的数据构建 UIImage 删除该数据...实际上,它删除了 most 的元数据 除了一组核心的 JFIF 和大小数据(使用 JPEG 时)。回想起来这是有道理的,因为它们的内部表示只是原始数据。但是,我没有在文档中找到任何关于元数据的明确声明。
- 鉴于先前的观点,将数据(带有元数据的图像)对象放入其中的主要两种方法
照片库要么将其写入临时文件
并阅读它
PHAssetChangeRequest::creationRequestForAssetFromImage(atFileURL:)
或使用PHAssetCreationRequest::forAsset
创建创建请求 然后使用PHAssetCreationRequest::addResource(with:data:options:)
将数据添加为照片。我选择了后者,因为它移动较少 部分。
所以我想所有这些都取代了漂亮、简洁的 ALAssetsLibrary::writeImage(toSavedPhotosAlbum:metadata:completionBlock:)
。
照片库的最终更改块最终是这样的:
var assetID: String?
PHPhotoLibrary.shared().performChanges({
let creationRequest = PHAssetCreationRequest.forAsset()
creationRequest.addResource(with: .photo, data: imageWithMetaData as Data, options: nil)
creationRequest.location = clLocation
assetID = creationRequest.placeholderForCreatedAsset?.localIdentifier
}) { success, error in ...