Saving Data as UIImage while preserving the original image characteristics

261 Views Asked by At

I am removing exif and location metadata from images using Photos and image I/O frameworks:

First I get Data from PHAssets:

let manager = PHImageManager()

manager.requestImageData(for: currentAsset, options: options) { (data, dataUTI, orientation, info) in
                if let data = data {
                    dataArray.append(data)
                }
            }

Then I use this function to remove metadata:

 fileprivate func removeMetadataFromData(data: Data) -> NSMutableData? {

            guard let source = CGImageSourceCreateWithData(data as CFData, nil) else {return nil}
            guard let type = CGImageSourceGetType(source) else {return nil}

            let count = CGImageSourceGetCount(source)
            let mutableData = NSMutableData(data: data)
            guard let destination = CGImageDestinationCreateWithData(mutableData, type, count, nil) else {return nil}
            let removeExifProperties: CFDictionary = [String(kCGImagePropertyExifDictionary) : kCFNull, String(kCGImagePropertyGPSDictionary): kCFNull] as CFDictionary
            for i in 0..<count {
                CGImageDestinationAddImageFromSource(destination, source, i, removeExifProperties)
            }

            guard CGImageDestinationFinalize(destination) else {return nil}

            return mutableData
}

Then I use this to create UIImage from NSMutableData objects that I get from previous function:

let image = UIImage(data: mutableData as Data)

and I save the image to user's library like so:

PHPhotoLibrary.shared().performChanges({
                        let request = PHAssetChangeRequest.creationRequestForAsset(from: image)
                        let placeholder = request.placeholderForCreatedAsset
                        let albumChangeRequest = PHAssetCollectionChangeRequest(for: collection)
                        if let placeholder = placeholder, let albumChangeRequest = albumChangeRequest {
                            albumChangeRequest.addAssets([placeholder] as NSArray)
                        }

            return mutableData
        }

The problem I have is that using this method, the output file is compressed, and also the name and DPI of the resulting image is different from the original image. I want to keep everything the same as the original image and just remove the metadata. Is there a way to do that?

1

There are 1 best solutions below

0
Rob On

The problem is the round-trip through UIImage. Just save the Data obtained from requestImageDataAndOrientation.

func saveCopyWithoutLocation(for asset: PHAsset) {
    let options = PHImageRequestOptions()

    manager.requestImageDataAndOrientation(for: asset, options: options) { data, dataUTI, orientation, info in
        guard let data = data else { return }

        self.library.performChanges {
            let request = PHAssetCreationRequest.forAsset()
            request.addResource(with: .photo, data: data, options: nil)
            request.location = nil
        } completionHandler: { success, error in
            if success {
                print("successful")
            } else {
                print(error?.localizedDescription ?? "no error?")
            }
        }
    }
}

Now, that only removes location. If you really want to remove more EXIF data obtained through CGImageSourceCreateWithData, you can do that. But just avoid an unnecessary round-trip through a UIImage. It is the whole purpose to use CGImageSource functions, namely that you can change metadata without changing the underlying image payload. (Round-tripping through UIImage is another way to strip meta data, but as you have discovered, it changes the image payload, too, though often not observable to the naked eye.)

So, if you want, just take the data from CGImageDestination functions directly, and pass that to PHAssetCreationRequest. But I might advise being a little more discriminating about which EXIF metadata you choose to remove, because some of it is important, non-confidential image data (e.g., likely the DPI is in there).

Regarding the filename, I'm not entirely sure you can control that. E.g., I've had images using the above location-stripping routine, and some preserve the file name in the copy, and others do not (and the logic of which applies is not immediately obvious to me; could be the sourceType). Obviously, you can use PHAssetChangeRequest rather than PHAssetCreationRequest, and you can just update the original PHAsset, and that would preserve the file name, but you might not have intended to edit the original asset and may have preferred to make a new copy.