2

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?

HemOdd
  • 697
  • 1
  • 7
  • 23
  • 1
    Why don't you simply draw the image in a new context? If you just remove the image orientation from the metadata your image might not have the proper orientation when displaying it – Leo Dabus May 04 '19 at 19:30
  • I don't have any problem with orientation. How about the file size, name, etc? will your method preserve those? – HemOdd May 04 '19 at 19:32
  • 1
    it is up to the programmer preserve or not the name and size. All you have to do is to create the image context using the original image as reference https://stackoverflow.com/a/42098812/2303865 and this https://developer.apple.com/documentation/uikit/uiimage/1649497-imagerendererformat – Leo Dabus May 04 '19 at 19:33

1 Answers1

0

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.

Rob
  • 415,655
  • 72
  • 787
  • 1,044