Swift 5 How to add exif data to image get from PHAsset - swift

I have this simply code to get an uiImage from Photo Library:
private func getDataOfMedia (asset:PHAsset) -> Data {
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true
requestOptions.isNetworkAccessAllowed = true
let imgManager = PHImageManager.default()
var imgData = Data()
// Request Image
imgManager.requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: requestOptions) { (uiimage, info) in
if let uiimage = uiimage {
DispatchQueue.main.async {
if let imageData = uiimage.jpegData(compressionQuality: 1) {
imgData = imageData
}
}
}
}
return imgData
}
but, of course, I can't see the camera data, location data and exif data of the image saved from func getDataOfMedia(asset: asset), but if I download the same image directly from Photo, I can see camera data, location data and exif data.
How can I do to add camera data, location data and exif data to data that I have from requestimage of PHAsset?
How can I add the unique id, for example asset.localIdentifier, to know that I have download this image?
UPDATE
I managed to extract camera data, location data and exif data from an image from an asset object, with this code:
private func getDataOfImageC (asset:PHAsset, completion: #escaping (Data) -> Void) {
//For get exif data
let options = PHContentEditingInputRequestOptions()
options.isNetworkAccessAllowed = true //download asset metadata from iCloud if needed
asset.requestContentEditingInput(with: options) { (contentEditingInput: PHContentEditingInput?, _) -> Void in
let fullImage = CIImage(contentsOf: contentEditingInput!.fullSizeImageURL!)
let image = UIImage(ciImage: fullImage!)
print(fullImage!.properties)
for (key, value) in fullImage!.properties {
print("key: \(key) - value: \(value)")
}
completion(image.jpegData(compressionQuality: 1)!)
}
}
but turning CIImage to UIImage to Data format, to save it locally, it loses all camera data, location data and exif data.
I hope that someone help me.

After some study, this the code that work for me to save image (not video) from photoLibrary with all properties.
private func saveDataOfImageCI (asset:PHAsset, urlMedia: URL) {
//For get exif data
let options = PHContentEditingInputRequestOptions()
options.isNetworkAccessAllowed = true //download asset metadata from iCloud if needed
asset.requestContentEditingInput(with: options) { (contentEditingInput: PHContentEditingInput?, _) -> Void in
let url = contentEditingInput!.fullSizeImageURL!
let fullImage = CIImage(contentsOf: contentEditingInput!.fullSizeImageURL!)
do {
try CIContext().writeJPEGRepresentation(of: fullImage!,
to: urlMedia,
colorSpace: (fullImage?.colorSpace)!)
} catch {
print("error: \(error.localizedDescription)")
}
//To print properties data (exif, camera data, ....)
for (key, value) in fullImage!.properties {
print("key: \(key) - value: \(value)")
}
}
}

Related

How can I convert a PHLivePhoto to UIImage?

In my app I have a section of code where I need to account for a PHLivePhoto type object and convert this to a UIImage. I believe it has to do with a PHAsset, PHAssetResource, or PHImageManager but unclear how to perform the conversion. What's a good approach on how to convert from PHLivePhoto -> UIImage? Thanks for the help!
if let livePhoto = object as? PHLivePhoto {
let livePhotoResources = PHAssetResource.assetResources(for: livePhoto)
print("\(livePhotoResources)")
// imageBucket is of type UIImage[]
// livePhoto is of type PHLivePhoto but I don't know how to convert this to type PHAsset
viewModel.imageBucket.append(convertImageAsset(asset: **WHAT_DO_I_INSERT_HERE**))
}
...
func convertImageAsset(asset: PHAsset) -> UIImage {
let manager = PHImageManager.default()
let option = PHImageRequestOptions()
var tmpImage = UIImage()
option.isSynchronous = true
manager.requestImage(
for: asset,
targetSize: CGSize(width: asset.pixelWidth, height: asset.pixelHeight),
contentMode: .aspectFit,
options: option,
resultHandler: {(result, info)->Void in
tmpImage = result!
})
return tmpImage
}
results in:
[<PHAssetResource: 0x28225cdc0> {
type: photo
uti: public.heic
filename: IMG_5442.heic
asset: (null)
locallyAvailable: YES
fileURL: file:///private/var/mobile/Containers/Data/Application/2FB56305-7600-4A8E-9C67-A71B4A4A9607/tmp/live-photo-bundle/75FD3D97-F13E-4F79-A6E9-F0743D443FDD.pvt/IMG_5442.HEIC
width: 0
height: 0
fileSize: 0
analysisType: unavailable
cplResourceType: Unknown
isCurrent: NO
isInCloud: NO
}, <PHAssetResource: 0x28226bc00> {
type: video_cmpl
uti: com.apple.quicktime-movie
filename: IMG_5442.mov
asset: (null)
locallyAvailable: YES
fileURL: file:///private/var/mobile/Containers/Data/Application/2FB56305-7600-4A8E-9C67-A71B4A4A9607/tmp/live-photo-bundle/75FD3D97-F13E-4F79-A6E9-F0743D443FDD.pvt/IMG_5442.MOV
width: 0
height: 0
fileSize: 0
analysisType: unavailable
cplResourceType: Unknown
isCurrent: NO
isInCloud: NO
}]
You need to use PHAsset to fetch the asset, then request image data from PHImageManager.default()
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
dismiss(animated: true)
guard let assetIdentifier = results.first?.assetIdentifier else {
return
}
if let phAsset = PHAsset.fetchAssets(withLocalIdentifiers: [assetIdentifier], options: nil).firstObject {
PHImageManager.default().requestImageDataAndOrientation(for: phAsset, options: nil) { [weak self] data, _, _, _ in
if let data = data, let image = UIImage(data: data) {
self?.viewModel.imageBucket.append(image)
}
}
}
}
To get assetIdentifier, you need to create PHPickerConfiguration object using the shared photo library. Creating a configuration without a photo library provides only asset data, and doesn't include asset identifiers.
var configuration = PHPickerConfiguration(photoLibrary: .shared())
// Set the filter type according to the user’s selection. .images is a filter to display images, including Live Photos.
configuration.filter = .images
// Set the mode to avoid transcoding, if possible, if your app supports arbitrary image/video encodings.
configuration.preferredAssetRepresentationMode = .current
// Set the selection limit.
configuration.selectionLimit = 1
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
present(picker, animated: true)
The problem with the accepted answer is that fetching a PHAsset will require photo library access, and one of the main advantages of PHPickerViewController is being able to get the photos without asking for permissions, and completely avoiding all the related edge cases.
So another way of getting a live photo's image would be:
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
for result in results {
// Live photos
if result.itemProvider.canLoadObject(ofClass: PHLivePhoto.self) {
result.itemProvider.loadObject(ofClass: PHLivePhoto.self, completionHandler: { livePhoto, error in
let resources = PHAssetResource.assetResources(for: livePhoto as! PHLivePhoto)
let photo = resources.first(where: { $0.type == .photo })!
let imageData = NSMutableData()
PHAssetResourceManager.default().requestData(for: photo, options: nil, dataReceivedHandler: { data in
imageData.append(data)
}, completionHandler: { error in
_ = UIImage(data: imageData as Data)!
})
})
}
}
}

Swift. How to extract the individual sub images from an HEIC image

I need to be able to load an heic image and extract and output all of the sub images as pngs similar to how preview does it. For example, if you open a dynamic heic wallpaper in preview, it shows all the images in the sidebar with their names:
How do you do this? I've tried to use NSImage like below. But that only outputs a single image:
let image = NSImage(byReferencing: url)
image.writePNG(toURL: newUrl)
You need to load the HEIC data, get its CGImageSource and its count. Then create a loop from 0 to count-1 and get each image at the corresponding index. You can create an array with those CGImages in memory or write them to disk (preferred). Note that this will take a while to be executed because of the size of the HEIC file 186MB. Each image extracted will be from 19MB to 28MB.
func extractHeicImages(from url: URL) throws {
let data = try Data(contentsOf: url)
let location = url.deletingLastPathComponent()
let pathExtension = url.pathExtension
let fileName = url.deletingPathExtension().lastPathComponent
let destinationFolder = location.appendingPathComponent(fileName)
guard pathExtension == "heic", let imageSource = CGImageSourceCreateWithData(data as CFData, nil) else { return }
let count = CGImageSourceGetCount(imageSource)
try FileManager.default.createDirectory(at: destinationFolder, withIntermediateDirectories: false, attributes: nil)
for index in 0..<count {
try autoreleasepool {
if let cgImage = CGImageSourceCreateImageAtIndex(imageSource, index, nil) {
let number = String(format: "#%05d", index)
let destinationURL = destinationFolder
.appendingPathComponent(fileName+number)
.appendingPathExtension(pathExtension)
try NSImage(cgImage: cgImage, size: .init(width: cgImage.width, height: cgImage.height))
.heic?
.write(to: destinationURL)
print("saved image " + number)
}
}
}
}
You will need these helpers as well to extract the cgimate from your image and also to get a HEIC data representation from them:
extension NSImage {
var heic: Data? { heic() }
var cgImage: CGImage? {
var rect = NSRect(origin: .zero, size: size)
return cgImage(forProposedRect: &rect, context: .current, hints: nil)
}
func heic(compressionQuality: CGFloat = 1) -> Data? {
guard
let mutableData = CFDataCreateMutable(nil, 0),
let destination = CGImageDestinationCreateWithData(mutableData, "public.heic" as CFString, 1, nil),
let cgImage = cgImage
else { return nil }
CGImageDestinationAddImage(destination, cgImage, [kCGImageDestinationLossyCompressionQuality: compressionQuality] as CFDictionary)
guard CGImageDestinationFinalize(destination) else { return nil }
return mutableData as Data
}
}
Playground testing. This assumes the "Catalina.heic" is located at your desktop.
let catalinaURL = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first!.appendingPathComponent("Catalina.heic")
do {
try extractHeicImages(from: catalinaURL)
} catch {
print(error)
}
Each subimage is represented by a NSBitmapImageRep. Loop the image reps, convert to png and save:
let imageReps = image.representations
for imageIndex in 0..<imageReps.count {
if let imageRep = imageReps[imageIndex] as? NSBitmapImageRep {
if let data = imageRep.representation(using: .png, properties: [:]) {
do {
let url = folderURL.appendingPathComponent("image \(imageIndex).png", isDirectory: false)
try data.write(to: url, options:[])
} catch {
print("Unexpected error: \(error).")
}
}
}
}
The conversion to png takes some time. Running the conversions in parallel is faster but I'm not sure if it's save:
DispatchQueue.concurrentPerform(iterations: imageReps.count) { iteration in
if let imageRep = imageReps[iteration] as? NSBitmapImageRep {
if let data = imageRep.representation(using: .png, properties: [:]) {
do {
let url = folderURL.appendingPathComponent("image \(iteration).png", isDirectory: false)
try data.write(to: url, options:[])
} catch {
print("Unexpected error: \(error).")
}
}
}
}

Modify/Edit PhAsset Image by Cropping

is there any way to modify/edit phasset image by cropping or edited one?
I have array of asset, I wanna crop image of asset by getting image from selected asset and passing it cropping controller and in return want to change that cropped image in selected asset.
There is my code to understand well
func presentCropViewController(with:IndexPath) {
self.allPhotos[with.item].getImage { (img) in
if let image = img{
self.indexPathForCropping = with
let cropViewController = CropViewController(image: image)
cropViewController.delegate = self
cropViewController.view.tintColor = UIColor.themeGreen()
self.present(cropViewController, animated: true, completion: nil)
}
}
}
after passing image from asset I got cropped image with this method
func cropViewController(_ cropViewController: CropViewController, didCropToImage image: UIImage, withRect cropRect: CGRect, angle: Int) {
cropViewController.dismiss(animated: true, completion: nil)
// Here i get the cropped image and want to update selected asset with this image
}
I'll appreciate if you mentioned down vote reason too so I prepare my question accordinglly
I figured out the solution may its not an efficient way to do but solved my problem
extension PHAsset {
func updateChanges(with img:UIImage,completion:#escaping(PHAsset?)->()){
PHPhotoLibrary.shared().performChanges({
// create cropped image into phphotolibrary
PHAssetChangeRequest.creationRequestForAsset(from: img)
}) { (success, error) in
if success{
// fetch request to get last created asset
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key:"creationDate", ascending: false)]
fetchOptions.fetchLimit = 1
let fetchResult: PHFetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)
if let asset = fetchResult.firstObject{
// replace your selected asset with new cropped one
completion(asset)
}else{
completion(nil)
}
}else{
completion(nil)
}
}
}
}
simply pass cropped/modified image and get new asset with same cropped/modified image

Swift 4 - How To load images using cache?

I have UIImageView and I want to download images in cache if exist, I've used extension func.
I have this code but not working:
extension UIImageView {
func loadImageUsingCache (_ urlString : String) {
let imageCache = NSCache<AnyObject, AnyObject>()
if let cachedImage = imageCache.object(forKey: urlString as AnyObject) {
self.image = cachedImage as? UIImage
return
}
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if data != nil {
if let image = UIImage(data: data!) {
imageCache.setObject(image, forKey: urlString as AnyObject)
DispatchQueue.main.async(execute: {
self.image = image
})
}
}
}.resume()
}
}
You're creating the new NSCache object for each image and not retain it.
You should create object variable instead of local one. It won't work in extension in this case. Also you can try to use URLCache.shared instead.
// It's Perfect Solution //
var imageCache = String: UIImage
class CustomImageView: UIImageView {
var lastImgUrlUsedToLoadImage: String?
func loadImage(with urlString: String) {
// set image to nil
self.image = nil
// set lastImgUrlUsedToLoadImage
lastImgUrlUsedToLoadImage = urlString
// check if image exists in cache
if let cachedImage = imageCache[urlString] {
self.image = cachedImage
return
}
// url for image location
guard let url = URL(string: urlString) else { return }
// fetch contents of URL
URLSession.shared.dataTask(with: url) { (data, response, error) in
// handle error
if let error = error {
print("Failed to load image with error", error.localizedDescription)
}
if self.lastImgUrlUsedToLoadImage != url.absoluteString {
return
}
// image data
guard let imageData = data else { return }
// create image using image data
let photoImage = UIImage(data: imageData)
// set key and value for image cache
imageCache[url.absoluteString] = photoImage
// set image
DispatchQueue.main.async {
self.image = photoImage
}
}.resume()
}
}

Firebase retrieve image from URL save with Firebase database

I have saved an image to the Firebase storage and also saved the imageURL to Firebase database, but how can I get the image back by the URL saved in firebase database? Here is how I save it
func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) {
userPhoto.image = image
dismissViewControllerAnimated(true, completion: nil)
var data = NSData()
data = UIImageJPEGRepresentation(userPhoto.image!, 0.8)!
// set upload path
let filePath = "\(FIRAuth.auth()!.currentUser!.uid)/\("userPhoto")"
let metaData = FIRStorageMetadata()
metaData.contentType = "image/jpg"
self.storageRef.child(filePath).putData(data, metadata: metaData){(metaData,error) in
if let error = error {
print(error.localizedDescription)
return
} else {
// store downloadURL
let downloadURL = metaData!.downloadURL()!.absoluteString
// store downloadURL at database
self.databaseRef.child("users").child(FIRAuth.auth()!.currentUser!.uid).updateChildValues(["userPhoto": downloadURL])
}
}
}
Here's one way of doing it, by downloading the data and then creating a UIImage for it:
self.storage.referenceForURL(url).dataWithMaxSize(25 * 1024 * 1024, completion: { (data, error) -> Void in
let image = UIImage(data: data!)
chatMessage.image = image!
self.messages.append(chatMessage)
self.tableView.reloadData()
self.scrollToBottom()
})
This code comes from the Zero To App talk at Google I/O. Complete code is available in this gist