How to compress Image without losing metadata?

91 views Asked by At

I have an app that can choose photo from gallery and then upload it to the server.

I have 2 requirements from the server side:

  1. Compress image to 1mb max
  2. Image should have metada/exif data

To select image from gallery, I'm using new SwiftUI .photosPicker

Then I use .loadTransferable that returns me image Data.

photo.loadTransferable(type: Data.self) { result in }

When I load this Data to the server, EXIF/metadata is there. And that's great. But I need to compress image data. I found a way to compress image data like this:

UIImage(data: data)?.jpegData(compressionQuality: 1)

And the problem is that after this compression, EXIF/metadata is LOST. UIImage cut it.

Question:

How to compress image Data without wrapping it to UIImage?

Or is there another way to compress image data and do not lose EXIF/metadata?

1

There are 1 answers

2
Marc Santisteban On

You're right, converting the image data to a UIImage and then compressing it with jpegData removes the EXIF data. Here's how you can achieve both compression and preserve EXIF data:

This approach uses CGImageSource and CGImageDestination to create a compressed version of the image while keeping the EXIF data intact.

func compressImage(data: Data, targetSize: Int = 1024 * 1024) throws -> Data {
  guard let source = CGImageSourceCreateWithData(data as CFData, nil) else {
    throw NSError(domain: "com.yourapp.error", code: 1, userInfo: ["message": "Failed to create image source"])
  }

  let options: CFDictionary = [
    kCGImageDestinationLosslessDecodeForEditing: kCFBooleanTrue,
    kCGImageDestinationAllowPartialDecoding: kCFBooleanTrue,
    kCGImagePropertyMagicNumber: kCFDataRef(data),
  ]

  let compressedData = NSMutableData()
  guard let destination = CGImageDestinationCreateWithData(compressedData as CFMutableData, kUTTypeJPEG, 1, options) else {
    throw NSError(domain: "com.yourapp.error", code: 1, userInfo: ["message": "Failed to create image destination"])
  }

  // Set compression quality (adjust as needed)
  let quality = 0.8

  let properties = [kCGImageDestinationJPEGQuality: quality]
  CGImageDestinationAddImageFromSource(destination, source, 0, properties)

  if !CGImageDestinationFinalize(destination) {
    throw NSError(domain: "com.yourapp.error", code: 1, userInfo: ["message": "Failed to finalize image destination"])
  }

  return compressedData as Data
}

However, there are several libraries like SDWebImage or SwiftyJPEG offer functionalities for compressing images while preserving metadata. These libraries might provide a more concise solution but come with the overhead of managing external dependencies