Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to compress of reduce the size of an image before uploading to Parse as PFFile? (Swift)

I was trying to upload an image file to Parse after taking photo directly on phone. But it throws an exception:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'PFFile cannot be larger than 10485760 bytes'

Here is my code:

In first view controller:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if (segue.identifier == "getImage")
    {
        var svc = segue.destinationViewController as! ClothesDetail
        svc.imagePassed = imageView.image
    }
}

In view controller that uploads image:

let imageData = UIImagePNGRepresentation(imagePassed)
let imageFile = PFFile(name: "\(picName).png", data: imageData)

var userpic = PFObject(className:"UserPic")
userpic["picImage"] = imageFile`

But I still need to upload that photo to Parse. Is there any way to reduce the size or resolution of the image?

like image 478
elvislkm Avatar asked Apr 19 '15 06:04

elvislkm


People also ask

How do I compress a photo before uploading?

We use TinyPNG.com to compress all the images you see on this site. Using TinyPNG is incredibly simple, once on the TinyPNG site you can simply drag your images into the upload field or click the upload field to select the images you want to compress on your computer.

How do I compress an image in Swift?

Create an extension Reduce the image height so it becomes smaller and uses less space. Create a new file called Extensions. swift and import SwiftUI at the top. Then, create an extension for UIImage, called aspectFittedToHeight, which will take a newHeight and return a smaller UIImage.


3 Answers

Yes you can use UIImageJPEGRepresentation instead of UIImagePNGRepresentation to reduce your image file size. You can just create an extension UIImage as follow:

Xcode 8.2 • Swift 3.0.2

extension UIImage {
    enum JPEGQuality: CGFloat {
        case lowest  = 0
        case low     = 0.25
        case medium  = 0.5
        case high    = 0.75
        case highest = 1
    }

    /// Returns the data for the specified image in JPEG format.
    /// If the image object’s underlying image data has been purged, calling this function forces that data to be reloaded into memory.
    /// - returns: A data object containing the JPEG data, or nil if there was a problem generating the data. This function may return nil if the image has no data or if the underlying CGImageRef contains data in an unsupported bitmap format.
    func jpeg(_ quality: JPEGQuality) -> Data? {
        return UIImageJPEGRepresentation(self, quality.rawValue)
    }
}

edit/update:

Xcode 10 Swift 4.2

extension UIImage {
    enum JPEGQuality: CGFloat {
        case lowest  = 0
        case low     = 0.25
        case medium  = 0.5
        case high    = 0.75
        case highest = 1
    }

    /// Returns the data for the specified image in JPEG format.
    /// If the image object’s underlying image data has been purged, calling this function forces that data to be reloaded into memory.
    /// - returns: A data object containing the JPEG data, or nil if there was a problem generating the data. This function may return nil if the image has no data or if the underlying CGImageRef contains data in an unsupported bitmap format.
    func jpeg(_ jpegQuality: JPEGQuality) -> Data? {
        return jpegData(compressionQuality: jpegQuality.rawValue)
    }
}

Usage:

if let imageData = image.jpeg(.lowest) {
    print(imageData.count)
}
like image 146
Leo Dabus Avatar answered Oct 09 '22 12:10

Leo Dabus


If you want to limit size of image to some concrete value u can do as follow:

import UIKit

extension UIImage {
    // MARK: - UIImage+Resize
    func compressTo(_ expectedSizeInMb:Int) -> UIImage? {
        let sizeInBytes = expectedSizeInMb * 1024 * 1024
        var needCompress:Bool = true
        var imgData:Data?
        var compressingValue:CGFloat = 1.0
        while (needCompress && compressingValue > 0.0) {
        if let data:Data = UIImageJPEGRepresentation(self, compressingValue) {
            if data.count < sizeInBytes {
                needCompress = false
                imgData = data
            } else {
                compressingValue -= 0.1
            }
        }
    }

    if let data = imgData {
        if (data.count < sizeInBytes) {
            return UIImage(data: data)
        }
    }
        return nil
    } 
}
like image 58
hbk Avatar answered Oct 09 '22 11:10

hbk


Using func jpegData(compressionQuality: CGFloat) -> Data? works well if you don't need to compress to a specific size. However, for certain images, I find it useful to be able to compress below a certain file size. In that case, jpegData is unreliable, and iterative compressing of an image results in plateauing out on filesize (and can be really expensive). Instead, I prefer to reduce the size of the UIImage itself, then convert to jpegData and check to see if the reduced size is beneath the value I chose (within a margin of error that I set). I adjust the compression step multiplier based on the ratio of the current filesize to the desired filesize to speed up the first iterations which are the most expensive.

Swift 5

extension UIImage {
    func resized(withPercentage percentage: CGFloat, isOpaque: Bool = true) -> UIImage? {
        let canvas = CGSize(width: size.width * percentage, height: size.height * percentage)
        let format = imageRendererFormat
        format.opaque = isOpaque
        return UIGraphicsImageRenderer(size: canvas, format: format).image {
            _ in draw(in: CGRect(origin: .zero, size: canvas))
        }
    }

    func compress(to kb: Int, allowedMargin: CGFloat = 0.2) -> Data {
        let bytes = kb * 1024
        var compression: CGFloat = 1.0
        let step: CGFloat = 0.05
        var holderImage = self
        var complete = false
        while(!complete) {
            if let data = holderImage.jpegData(compressionQuality: 1.0) {
                let ratio = data.count / bytes
                if data.count < Int(CGFloat(bytes) * (1 + allowedMargin)) {
                    complete = true
                    return data
                } else {
                    let multiplier:CGFloat = CGFloat((ratio / 5) + 1)
                    compression -= (step * multiplier)
                }
            }
            
            guard let newImage = holderImage.resized(withPercentage: compression) else { break }
            holderImage = newImage
        }
        return Data()
    }
}

And usage:

let data = image.compress(to: 300)

UIImage resized extension comes from: How do I resize the UIImage to reduce upload image size

like image 13
Iron John Bonney Avatar answered Oct 09 '22 11:10

Iron John Bonney