In Swift, how to reduce an image file size to a specific size? e.g 1MB

We need to manage the photo sizes due to storage and transfer constrains.

What is the need? After taking/importing photo to app, automatically reduce the size of photo while keeping the aspect ratio. For exampe, from 2.3MB at 4:3 ratio to 500KB at 4:3 ration.


Can you share your ideas how this can be accomplished.

Best

Felipe

Replies

Did you try using UIImageJPEGRepresentation ?


You cannot specify a size, but an image quality:


            let imageData = UIImageJPEGRepresentation(myImage!, 0.2)

If you really want to get a certain size, you have to use something like binary search to find the correct quality parameter.

Thanks Claude31 - the JPEG compression will reduce the file size but final size will depend very much on the file size you started with.

This sounds interesting ahltorp. Can you elaborate how you would go about it. Thanks

There are many ways to reduce the overall byte (storage) size of an image. All ultimately deal with quality. Also, some of the following are not mutually exclusive. You can thus apply one or more of the following.


  • Reduce the image dimensions
  • As mentioned in other replies, apply compression.
  • Reduce the bit-depth. One common thing that used to be done was to use 8-bit color (8 bit per pixel) and using a custom CLUT (color lookup table). Along with ditering, you can get decent images at a fraction of their storage size.
  • If the image has alpha, pre-multiply the alpha and store that way. e.g. instead of 32-bits per pixel (8 bits each for RGBA), you end up with 24-bits per pixel (25% savings).

Something like this:


func jpegImage(image: UIImage, maxSize: Int, minSize: Int, times: Int) -> Data? {
    var maxQuality: CGFloat = 1.0
    var minQuality: CGFloat = 0.0
    var bestData: Data?
    for _ in 1...times {
        let thisQuality = (maxQuality + minQuality) / 2
        guard let data = UIImageJPEGRepresentation(image, thisQuality) else { return nil }
        let thisSize = data.count
        if thisSize > maxSize {
            maxQuality = thisQuality
        } else {
            minQuality = thisQuality
            bestData = data
            if thisSize > minSize {
                return bestData
            }
        }
    }

    return bestData
}


where you call it like this:


jpegImage(image: image, maxSize: 500000, minSize: 400000, times: 10)


It will try to get a file between a maximum and minimum size of maxSize and minSize, but only try times times. If it fails within that time, it will return nil.

Add a Comment

It depends if you want excatly 1MB or approximately. But what would you if size was less than 1MB ??


So, I would suggest a simple way:

- read the original image size.

Then depending on its size :

let roughSize = Int(sizeInBytes / 1_000_000)

- if over 10 MB, apply the maximum compression you accept for the quality level : 0.1 for instance

- if between 1 and 10,

let quality = roughSize / 10

apply quality as compression ration

- below 1, do nothing

Rally appreciate everyone for the ideas and sample code. Will give it ago and see how it goes!!

Hi, this is very helpful, however not wanting to highjack this topic, I have one question about your code here in conjunction with a different problem/question asked by me here today in this forum.


What is the replacement code using macos, in other words what is the macos equivalent of UIImageJPEGRepresentation?

For MacOS and IOS, you have, In Core Image / Context


func jpegRepresentation(of image: CIImage, colorSpace: CGColorSpace, options: [CIImageRepresentationOption : Any] = [:]) -> Data?


For IOS, it is now

UIImageJPEGRepresentation has been replaced by instance method UIImage.jpegData(compressionQuality:)