Compress image size to below 1 MB in swift iOS - ios

In iOS swift need to compress image size to below 1 MB by following this thread How to compress of reduce the size of an image before uploading to Parse as PFFile? (Swift) tried with below code it returns nil.
extension UIImage {
// MARK: - UIImage+Compression
func compressTo(_ expectedSizeBelowMb:Int) -> Data? {
let sizeInBytes = expectedSizeBelowMb * 1024 * 1024
var needCompress:Bool = true
var imgData:Data?
var compressingValue:CGFloat = 1.0
while (needCompress && compressingValue > 0.0) {
if let data:Data = self.jpegData(compressionQuality: compressingValue) {
if data.count < sizeInBytes {
needCompress = false
imgData = data
} else {
compressingValue -= 0.1
}
}
}
if let data = imgData, data.count < sizeInBytes {
return data
}
return nil
}
}
Can anyone suggest me correct way of compressing image size to below 1 MB.

You can try this code:
extension UIImage {
func resizedTo1MB() -> UIImage? {
guard let imageData = self.pngData() else { return nil }
let megaByte = 1000.0
var resizingImage = self
var imageSizeKB = Double(imageData.count) / megaByte // ! Or devide for 1024 if you need KB but not kB
while imageSizeKB > megaByte { // ! Or use 1024 if you need KB but not kB
guard let resizedImage = resizingImage.resized(withPercentage: 0.5),
let imageData = resizedImage.pngData() else { return nil }
resizingImage = resizedImage
imageSizeKB = Double(imageData.count) / megaByte // ! Or devide for 1024 if you need KB but not kB
}
return resizingImage
}
}

Related

Resize UIImage to have 512 x 512 resolution and keeping size less than 100 KB in Swift

How can I resize UIImage to have 512 x 512 resolution and keeping size less than 100 KB. I am successful to achieve the required resolution but size is always more than 100 KB.
You could compress the image using jpegData(compressionQuality:) method to achieve size less than a particular value using this extension:
extension UIImage {
func jpegData(sizeLessThan sizeInKB: Double) -> Data? {
var compressionQuality = CGFloat(1)
var compressedData = jpegData(compressionQuality: compressionQuality)
while (Double(compressedData?.count ?? 0) / 1024) > sizeInKB && compressionQuality >= 0 {
compressionQuality -= 0.1
compressedData = jpegData(compressionQuality: compressionQuality)
}
return compressedData
}
}
Usage:
var image: UIImage?
let imageData = image?.jpegData(sizeLessThan: 100)
print(imageData)

Reduce memory consumption while loading gif images in UIImageView

I want to show gif image in a UIImageView and with the code below (source: https://iosdevcenters.blogspot.com/2016/08/load-gif-image-in-swift_22.html, *I did not understand all the codes), I am able to display gif images. However, the memory consumption seems high (tested on real device). Is there any way to modify the code below to reduce the memory consumption?
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let url = "https://cdn-images-1.medium.com/max/800/1*oDqXedYUMyhWzN48pUjHyw.gif"
let gifImage = UIImage.gifImageWithURL(url)
imageView.image = gifImage
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
fileprivate func < <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l < r
case (nil, _?):
return true
default:
return false
}
}
extension UIImage {
public class func gifImageWithData(_ data: Data) -> UIImage? {
guard let source = CGImageSourceCreateWithData(data as CFData, nil) else {
print("image doesn't exist")
return nil
}
return UIImage.animatedImageWithSource(source)
}
public class func gifImageWithURL(_ gifUrl:String) -> UIImage? {
guard let bundleURL:URL? = URL(string: gifUrl) else {
return nil
}
guard let imageData = try? Data(contentsOf: bundleURL!) else {
return nil
}
return gifImageWithData(imageData)
}
public class func gifImageWithName(_ name: String) -> UIImage? {
guard let bundleURL = Bundle.main
.url(forResource: name, withExtension: "gif") else {
return nil
}
guard let imageData = try? Data(contentsOf: bundleURL) else {
return nil
}
return gifImageWithData(imageData)
}
class func delayForImageAtIndex(_ index: Int, source: CGImageSource!) -> Double {
var delay = 0.1
let cfProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil)
let gifProperties: CFDictionary = unsafeBitCast(
CFDictionaryGetValue(cfProperties,
Unmanaged.passUnretained(kCGImagePropertyGIFDictionary).toOpaque()),
to: CFDictionary.self)
var delayObject: AnyObject = unsafeBitCast(
CFDictionaryGetValue(gifProperties,
Unmanaged.passUnretained(kCGImagePropertyGIFUnclampedDelayTime).toOpaque()),
to: AnyObject.self)
if delayObject.doubleValue == 0 {
delayObject = unsafeBitCast(CFDictionaryGetValue(gifProperties,
Unmanaged.passUnretained(kCGImagePropertyGIFDelayTime).toOpaque()), to: AnyObject.self)
}
delay = delayObject as! Double
if delay < 0.1 {
delay = 0.1
}
return delay
}
class func gcdForPair(_ a: Int?, _ b: Int?) -> Int {
var a = a
var b = b
if b == nil || a == nil {
if b != nil {
return b!
} else if a != nil {
return a!
} else {
return 0
}
}
if a < b {
let c = a
a = b
b = c
}
var rest: Int
while true {
rest = a! % b!
if rest == 0 {
return b!
} else {
a = b
b = rest
}
}
}
class func gcdForArray(_ array: Array<Int>) -> Int {
if array.isEmpty {
return 1
}
var gcd = array[0]
for val in array {
gcd = UIImage.gcdForPair(val, gcd)
}
return gcd
}
class func animatedImageWithSource(_ source: CGImageSource) -> UIImage? {
let count = CGImageSourceGetCount(source)
var images = [CGImage]()
var delays = [Int]()
for i in 0..<count {
if let image = CGImageSourceCreateImageAtIndex(source, i, nil) {
images.append(image)
}
let delaySeconds = UIImage.delayForImageAtIndex(Int(i),
source: source)
delays.append(Int(delaySeconds * 1000.0)) // Seconds to ms
}
let duration: Int = {
var sum = 0
for val: Int in delays {
sum += val
}
return sum
}()
let gcd = gcdForArray(delays)
var frames = [UIImage]()
var frame: UIImage
var frameCount: Int
for i in 0..<count {
frame = UIImage(cgImage: images[Int(i)])
frameCount = Int(delays[Int(i)] / gcd)
for _ in 0..<frameCount {
frames.append(frame)
}
}
let animation = UIImage.animatedImage(with: frames,
duration: Double(duration) / 1000.0)
return animation
}
}
When I render the image as normal png image, the consumption is around 10MB.
The GIF in question has a resolution of 480×288 and contains 10 frames.
Considering that UIImageView stores frames as 4-byte RGBA, this GIF occupies 4 × 10 × 480 × 288 = 5 529 600 bytes in RAM, which is more than 5 megabytes.
There are numerous ways to mitigate that, but only one of them puts no additional strain on the CPU; the others are mere CPU-to-RAM trade-offs.
The method I`m talking about is subclassing UIImageView and loading your GIFs by hand, preserving their internal representation (indexed image + palette). It would allow you to cut the memory usage fourfold.
N.B.: even though GIFs may be stored as full images for each frame (which is the case for the GIF in question), many are not. On the contrary, most of the frames can only contain the pixels that have changed since the previous one. Thus, in general the internal GIF representation only allows to display frames in direct order.
Other methods of saving RAM include e.g. re-reading every frame from disk prior to displaying it, which is certainly not good for battery life.
To display GIFs with less memory consumption, try BBWebImage.
BBWebImage will decide how many image frames are decoded and cached depending on current memory usage. If free memory is not enough, only part of image frames are decoded and cached.
For Swift 4:
// BBAnimatedImageView (subclass UIImageView) displays animated image
imageView = BBAnimatedImageView(frame: frame)
// Load and display gif
imageView.bb_setImage(with: url,
placeholder: UIImage(named: "placeholder"))
{ (image: UIImage?, data: Data?, error: Error?, cacheType: BBImageCacheType) in
// Do something when finish loading
}

How to get image file size in Swift?

I am using
UIImagePickerControllerDelegate,
UINavigationControllerDelegate,
UIPopoverControllerDelegate
these delegates for choosing image from my gallery or my camera. So, how can I get image file size after choosing an image?
I want to use this:
let filePath = "your path here"
var fileSize : UInt64 = 0
do {
let attr : NSDictionary? = try NSFileManager.defaultManager().attributesOfItemAtPath(filePath)
if let _attr = attr {
fileSize = _attr.fileSize();
print(fileSize)
}
} catch {
}
but here I need a path, but how can I get without a path, just by image file?
Please check the google for 1 kb to bytes it will be 1000.
https://www.google.com/search?q=1+kb+%3D+how+many+bytes&oq=1+kb+%3D+how+many+bytes&aqs=chrome..69i57.8999j0j1&sourceid=chrome&ie=UTF-8
So while getting the proper size I’ve added multiple scenario by adding image in App Bundle and in photos in simulator.
Well the image which I took from my Mac was of 299.0 KB.
Scenario 1: Adding image to Application Bundle
On adding image in your Xcode the size of the image will remain same in project directory. But you get it from its path the size will be reduced to 257.0 KB. Which is the actual size of the image used in the device or simulator.
guard let aStrUrl = Bundle.main.path(forResource: "1", ofType: "png") else { return }
let aUrl = URL(fileURLWithPath: aStrUrl)
print("Img size = \((Double(aUrl.fileSize) / 1000.00).rounded()) KB")
extension URL {
var attributes: [FileAttributeKey : Any]? {
do {
return try FileManager.default.attributesOfItem(atPath: path)
} catch let error as NSError {
print("FileAttribute error: \(error)")
}
return nil
}
var fileSize: UInt64 {
return attributes?[.size] as? UInt64 ?? UInt64(0)
}
var fileSizeString: String {
return ByteCountFormatter.string(fromByteCount: Int64(fileSize), countStyle: .file)
}
var creationDate: Date? {
return attributes?[.creationDate] as? Date
}
}
Scenario 2: Adding image to Photos in Simulator
On adding image to photos in simulator or device the size of the image increased from 299.0 KB to 393.0 KB. Which is the actual size of the image stored in the device or simulator’s document directory.
Swift 4 and earlier
var image = info[UIImagePickerControllerOriginalImage] as! UIImage
var imgData: NSData = NSData(data: UIImageJPEGRepresentation((image), 1))
// var imgData: NSData = UIImagePNGRepresentation(image)
// you can also replace UIImageJPEGRepresentation with UIImagePNGRepresentation.
var imageSize: Int = imgData.count
print("size of image in KB: %f ", Double(imageSize) / 1000.0)
Swift 5
let image = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
let imgData = NSData(data: image.jpegData(compressionQuality: 1)!)
var imageSize: Int = imgData.count
print("actual size of image in KB: %f ", Double(imageSize) / 1000.0)
By adding .rounded() it will give you 393.0 KB and without using it it will give 393.442 KB. So please check the image size manually once using the above code. As the size of image may vary in different devices and mac. I've check it only on mac mini and simulator iPhone XS.
extension UIImage {
public enum DataUnits: String {
case byte, kilobyte, megabyte, gigabyte
}
func getSizeIn(_ type: DataUnits)-> String {
guard let data = self.pngData() else {
return ""
}
var size: Double = 0.0
switch type {
case .byte:
size = Double(data.count)
case .kilobyte:
size = Double(data.count) / 1024
case .megabyte:
size = Double(data.count) / 1024 / 1024
case .gigabyte:
size = Double(data.count) / 1024 / 1024 / 1024
}
return String(format: "%.2f", size)
}
}
Usage example : print("Image size \(yourImage.getSizeIn(.megabyte)) mb")
Swift 3/4:
if let imageData = UIImagePNGRepresentation(image) {
let bytes = imageData.count
let kB = Double(bytes) / 1000.0 // Note the difference
let KB = Double(bytes) / 1024.0 // Note the difference
}
Please note the difference between kB and KB. Answering here because in my case we had an issue while we considered kilobyte as 1024 bytes but server side considered it as 1000 bytes which caused an issue. Link to learn more.
PS. Almost sure you'll go with kB (1000).
Details
Xcode 10.2.1 (10E1001), Swift 5
Solution
extension String {
func getNumbers() -> [NSNumber] {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
let charset = CharacterSet.init(charactersIn: " ,.")
return matches(for: "[+-]?([0-9]+([., ][0-9]*)*|[.][0-9]+)").compactMap { string in
return formatter.number(from: string.trimmingCharacters(in: charset))
}
}
// https://stackoverflow.com/a/54900097/4488252
func matches(for regex: String) -> [String] {
guard let regex = try? NSRegularExpression(pattern: regex, options: [.caseInsensitive]) else { return [] }
let matches = regex.matches(in: self, options: [], range: NSMakeRange(0, self.count))
return matches.compactMap { match in
guard let range = Range(match.range, in: self) else { return nil }
return String(self[range])
}
}
}
extension UIImage {
func getFileSizeInfo(allowedUnits: ByteCountFormatter.Units = .useMB,
countStyle: ByteCountFormatter.CountStyle = .file) -> String? {
// https://developer.apple.com/documentation/foundation/bytecountformatter
let formatter = ByteCountFormatter()
formatter.allowedUnits = allowedUnits
formatter.countStyle = countStyle
return getSizeInfo(formatter: formatter)
}
func getFileSize(allowedUnits: ByteCountFormatter.Units = .useMB,
countStyle: ByteCountFormatter.CountStyle = .memory) -> Double? {
guard let num = getFileSizeInfo(allowedUnits: allowedUnits, countStyle: countStyle)?.getNumbers().first else { return nil }
return Double(truncating: num)
}
func getSizeInfo(formatter: ByteCountFormatter, compressionQuality: CGFloat = 1.0) -> String? {
guard let imageData = jpegData(compressionQuality: compressionQuality) else { return nil }
return formatter.string(fromByteCount: Int64(imageData.count))
}
}
Usage
guard let image = UIImage(named: "img") else { return }
if let imageSizeInfo = image.getFileSizeInfo() {
print("\(imageSizeInfo), \(type(of: imageSizeInfo))") // 51.9 MB, String
}
if let imageSizeInfo = image.getFileSizeInfo(allowedUnits: .useBytes, countStyle: .file) {
print("\(imageSizeInfo), \(type(of: imageSizeInfo))") // 54,411,697 bytes, String
}
if let imageSizeInfo = image.getFileSizeInfo(allowedUnits: .useKB, countStyle: .decimal) {
print("\(imageSizeInfo), \(type(of: imageSizeInfo))") // 54,412 KB, String
}
if let size = image.getFileSize() {
print("\(size), \(type(of: size))") // 51.9, Double
}
Swift 3
let uploadData = UIImagePNGRepresentation(image)
let array = [UInt8](uploadData)
print("Image size in bytes:\(array.count)")
try this for getting size from url
func fileSize(url: URL) -> String? {
var fileSize:Int?
do {
let resources = try url.resourceValues(forKeys:[.fileSizeKey])
fileSize = resources.fileSize!
print ("\(String(describing: fileSize))")
} catch {
print("Error: \(error)")
}
// bytes
if fileSize! < 999 {
return String(format: "%lu bytes", CUnsignedLong(bitPattern: fileSize!))
}
// KB
var floatSize = Float(fileSize! / 1000)
if floatSize < 999 {
return String(format: "%.1f KB", floatSize)
}
// MB
floatSize = floatSize / 1000
if floatSize < 999 {
return String(format: "%.1f MB", floatSize)
}
// GB
floatSize = floatSize / 1000
return String(format: "%.1f GB", floatSize)
}
Use Example
let sizeInString = fileSize(url: url)
print("FileSize = "+sizeInString!)
let selectedImage = info[UIImagePickerControllerOriginalImage] as! UIImage
let selectedImageData: NSData = NSData(data:UIImageJPEGRepresentation((selectedImage), 1))
let selectedImageSize:Int = selectedImageData.length
print("Image Size: %f KB", selectedImageSize /1024.0)
let data = UIImageJPEGRepresentation(image, 1)
let imageSize = data?.count
Duplicate of How to get the size of a UIImage in KB?
let imageData = UIImageJPEGRepresentation(image, 1)
let imageSize = imageData?.count
UIImageJPEGRepresentation — returns the Data object for the specified image in JPEG format. The value 1.0 represents the least compression (close to original image).
imageData?.count — return data length (chars count equals bytes).
Important! UIImageJPEGRepresentation or UIImagePNGRepresentation will not return the original image. But if use given Data as source for uploading - than file size be the same as on the server (even using compression).
Swift 4.2
let jpegData = image.jpegData(compressionQuality: 1.0)
let jpegSize: Int = jpegData?.count ?? 0
print("size of jpeg image in KB: %f ", Double(jpegSize) / 1024.0)
Try this code (Swift 4.2)
extension URL {
var attributes: [FileAttributeKey : Any]? {
do {
return try FileManager.default.attributesOfItem(atPath: path)
} catch let error as NSError {
print("FileAttribute error: \(error)")
}
return nil
}
var fileSize: UInt64 {
return attributes?[.size] as? UInt64 ?? UInt64(0)
}
var fileSizeString: String {
return ByteCountFormatter.string(fromByteCount: Int64(fileSize), countStyle: .file)
}
var creationDate: Date? {
return attributes?[.creationDate] as? Date
}
}
And use example
guard let aStrUrl = Bundle.main.path(forResource: "example_image", ofType: "jpg") else { return }
let aUrl = URL(fileURLWithPath: aStrUrl)
print("Img size = \((Double(aUrl.fileSize) / 1000.00).rounded()) KB")
//Swift 4
if let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
///check image Size
let imgData = NSData(data: UIImageJPEGRepresentation((pickedImage), 1)!)
let imageSize: Int = imgData.count
print("size of image in KB: %f ", Double(imageSize) / 1024.0)
print("size of image in MB: %f ", Double(imageSize) / 1024.0 / 1024)
}
I make work around data units conversion :
Bytes -> KB -> MB -> GB -> ... -> Extremest Monster Data
enum dataUnits:CaseIterable {
case B //Byte
case KB //kilobyte
case MB //megabyte
case GB //gigabyte
case TB //terabyte
case PB //petabyte
case EB //exabyte
case ZB //zettabyte
case YB //yottabyte
case BD //Big Data
case BBx // Extra Big Bytes
case BBxx // 2 time Extra Big Bytes
case BBxxx // 3 time Extra Big Bytes
case BBxxxx // 4 time Extra Big Bytes
case MBB // Monster Big Bytes
}
func convertStorageUnit(data n:Double,inputDataUnit unitLevel:Int,roundPoint:Int = 2,nG:Double = 1000.0)->String{
if(n>=nG){
return convertStorageUnit(data:n/1024,inputDataUnit:unitLevel+1)
}else{
let ut = unitLevel > dataUnits.allCases.count + 1 ? "Extreme Monster Data" : dataUnits.allCases.map{"\($0)"}[unitLevel]
return "\(String(format:"%.\(roundPoint)f",n)) \(ut)"
}
}
print(
convertStorageUnit(data:99922323343439789798789898989897987945454545920,
inputDataUnit:dataUnits.allCases.firstIndex(of: .B)!,roundPoint: 0)
)
output : 8.87 PB
Note: Input data length should be less than 64-bit OR Change data type According
Try this
import Darwin
...
let size = malloc_size(&_attr)

Convert UIImage to byte array in swift

How can I convert a UIimage into a Byte Array, so I can upload it into my web service?
You can actually use a couple of lines to do it
guard let image = UIImage(named: "someImage"),
let data = image.jpegData(compressionQuality: 1.0) else { return }
// OR
guard let image = UIImage(named: "someImage"),
let data = image.pngData() else { return }
The number should range from 0.0 to 1.0 and sets the jpeg quality. PNG is lossless so there is no need for compression quality identifier but be aware that the file size can be about 10 times higher
--- update ---
Updated for Swift 5.1
You can convert UIImage to NSData and pass it to this method
func getArrayOfBytesFromImage(imageData:NSData) -> NSMutableArray
{
// the number of elements:
let count = imageData.length / sizeof(UInt8)
// create array of appropriate length:
var bytes = [UInt8](count: count, repeatedValue: 0)
// copy bytes into array
imageData.getBytes(&bytes, length:count * sizeof(UInt8))
var byteArray:NSMutableArray = NSMutableArray()
for (var i = 0; i < count; i++) {
byteArray.addObject(NSNumber(unsignedChar: bytes[i]))
}
return byteArray
}
Swift 5, iOS 14 version based on toofani answer, minimal changes
func getArrayOfBytesFromImage(imageData:NSData) -> Array<UInt8>
{
// the number of elements:
let count = imageData.length / MemoryLayout<Int8>.size
// create array of appropriate length:
var bytes = [UInt8](repeating: 0, count: count)
// copy bytes into array
imageData.getBytes(&bytes, length:count * MemoryLayout<Int8>.size)
var byteArray:Array = Array<UInt8>()
for i in 0 ..< count {
byteArray.append(bytes[i])
}
return byteArray
}
So a complete sequence looks like this... assuming I got a UIImage I extract the data and then recombine it.
let data = imageX.pngData()
bytes = getArrayOfBytesFromImage(imageData: data! as NSData)
let datos: NSData = NSData(bytes: bytes, length: bytes.count)
newImage = UIImage(data: datos as Data) // Note it's optional. Don't force unwrap!!!

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?
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)
}
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
}
}
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
//image compression
func resizeImage(image: UIImage) -> UIImage {
var actualHeight: Float = Float(image.size.height)
var actualWidth: Float = Float(image.size.width)
let maxHeight: Float = 300.0
let maxWidth: Float = 400.0
var imgRatio: Float = actualWidth / actualHeight
let maxRatio: Float = maxWidth / maxHeight
let compressionQuality: Float = 0.5
//50 percent compression
if actualHeight > maxHeight || actualWidth > maxWidth {
if imgRatio < maxRatio {
//adjust width according to maxHeight
imgRatio = maxHeight / actualHeight
actualWidth = imgRatio * actualWidth
actualHeight = maxHeight
}
else if imgRatio > maxRatio {
//adjust height according to maxWidth
imgRatio = maxWidth / actualWidth
actualHeight = imgRatio * actualHeight
actualWidth = maxWidth
}
else {
actualHeight = maxHeight
actualWidth = maxWidth
}
}
let rect = CGRectMake(0.0, 0.0, CGFloat(actualWidth), CGFloat(actualHeight))
UIGraphicsBeginImageContext(rect.size)
image.drawInRect(rect)
let img = UIGraphicsGetImageFromCurrentImageContext()
let imageData = UIImageJPEGRepresentation(img!,CGFloat(compressionQuality))
UIGraphicsEndImageContext()
return UIImage(data: imageData!)!
}
Jus Fixing for Xcode 7, tested on 09/21/2015 and working fine:
Just create an extension UIImage as follow:
extension UIImage
{
var highestQualityJPEGNSData: NSData { return UIImageJPEGRepresentation(self, 1.0)! }
var highQualityJPEGNSData: NSData { return UIImageJPEGRepresentation(self, 0.75)!}
var mediumQualityJPEGNSData: NSData { return UIImageJPEGRepresentation(self, 0.5)! }
var lowQualityJPEGNSData: NSData { return UIImageJPEGRepresentation(self, 0.25)!}
var lowestQualityJPEGNSData: NSData { return UIImageJPEGRepresentation(self, 0.0)! }
}
Then you can use it like this:
let imageData = imagePassed.lowestQualityJPEGNSData
Swift 4 Binary Approach to compress image
I believe it is quite late to answer this question but here is my solution to the question which is optimized I am using Binary search to find the optimal value. So, for example, say by normal subtraction approach to reach 62% would require 38 compression attempts, the *Binary search** approach would reach the required solution in max log(100) = around 7 attempts.
However, would also like to inform you that the UIImageJPEGRepresentation function does not behave linearly especially when the number reaches near 1. Here is the screen grab where we can see that the image stops compressing after the float value is > 0.995. The behaviour is quite unpredictable so better to have a delta buffer that would handle such cases.
Here is the code for it
extension UIImage {
func resizeToApprox(sizeInMB: Double, deltaInMB: Double = 0.2) -> Data {
let allowedSizeInBytes = Int(sizeInMB * 1024 * 1024)
let deltaInBytes = Int(deltaInMB * 1024 * 1024)
let fullResImage = UIImageJPEGRepresentation(self, 1.0)
if (fullResImage?.count)! < Int(deltaInBytes + allowedSizeInBytes) {
return fullResImage!
}
var i = 0
var left:CGFloat = 0.0, right: CGFloat = 1.0
var mid = (left + right) / 2.0
var newResImage = UIImageJPEGRepresentation(self, mid)
while (true) {
i += 1
if (i > 13) {
print("Compression ran too many times ") // ideally max should be 7 times as log(base 2) 100 = 6.6
break
}
print("mid = \(mid)")
if ((newResImage?.count)! < (allowedSizeInBytes - deltaInBytes)) {
left = mid
} else if ((newResImage?.count)! > (allowedSizeInBytes + deltaInBytes)) {
right = mid
} else {
print("loop ran \(i) times")
return newResImage!
}
mid = (left + right) / 2.0
newResImage = UIImageJPEGRepresentation(self, mid)
}
return UIImageJPEGRepresentation(self, 0.5)!
}
}
Swift 4.2 update. I created this extension to reduce UIImage size. Here you have two method, the first take a percentage and the second reduce the image to 1MB. Of course you can change the second method to become 1KB or any size you want.
import UIKit
extension UIImage {
func resized(withPercentage percentage: CGFloat) -> UIImage? {
let canvasSize = CGSize(width: size.width * percentage, height: size.height * percentage)
UIGraphicsBeginImageContextWithOptions(canvasSize, false, scale)
defer { UIGraphicsEndImageContext() }
draw(in: CGRect(origin: .zero, size: canvasSize))
return UIGraphicsGetImageFromCurrentImageContext()
}
func resizedTo1MB() -> UIImage? {
guard let imageData = self.pngData() else { return nil }
let megaByte = 1000.0
var resizingImage = self
var imageSizeKB = Double(imageData.count) / megaByte // ! Or devide for 1024 if you need KB but not kB
while imageSizeKB > megaByte { // ! Or use 1024 if you need KB but not kB
guard let resizedImage = resizingImage.resized(withPercentage: 0.5),
let imageData = resizedImage.pngData() else { return nil }
resizingImage = resizedImage
imageSizeKB = Double(imageData.count) / megaByte // ! Or devide for 1024 if you need KB but not kB
}
return resizingImage
}
}
NEW BETTER WAY
import UIKit
extension UIImage {
func compress(maxKb: Double) -> Data? {
let quality: CGFloat = maxKb / self.sizeAsKb()
let compressedData: Data? = self.jpegData(compressionQuality: quality)
return compressedData
}
func sizeAsKb() -> Double {
Double(self.pngData()?.count ?? 0 / 1024)
}
}
// To use
let compressedImage = image.compress(maxKb: 100)
OLD BAD WAY
That's very simple with UIImage extension
extension UIImage {
func resizeByByte(maxByte: Int, completion: #escaping (Data) -> Void) {
var compressQuality: CGFloat = 1
var imageData = Data()
var imageByte = UIImageJPEGRepresentation(self, 1)?.count
while imageByte! > maxByte {
imageData = UIImageJPEGRepresentation(self, compressQuality)!
imageByte = UIImageJPEGRepresentation(self, compressQuality)?.count
compressQuality -= 0.1
}
if maxByte > imageByte! {
completion(imageData)
} else {
completion(UIImageJPEGRepresentation(self, 1)!)
}
}
SWIFT 5
extension UIImage {
func resizeByByte(maxByte: Int, completion: #escaping (Data) -> Void) {
var compressQuality: CGFloat = 1
var imageData = Data()
var imageByte = self.jpegData(compressionQuality: 1)?.count
while imageByte! > maxByte {
imageData = self.jpegData(compressionQuality: compressQuality)!
imageByte = self.jpegData(compressionQuality: compressQuality)?.count
compressQuality -= 0.1
}
if maxByte > imageByte! {
completion(imageData)
} else {
completion(self.jpegData(compressionQuality: 1)!)
}
}
}
to use
// max 300kb
image.resizeByByte(maxByte: 300000) { (resizedData) in
print("image size: \(resizedData.count)")
}
In Swift 5 as #Thiago Arreguy answer:
extension UIImage {
var highestQualityJPEGNSData: Data { return self.jpegData(compressionQuality: 1.0)! }
var highQualityJPEGNSData: Data { return self.jpegData(compressionQuality: 0.75)!}
var mediumQualityJPEGNSData: Data { return self.jpegData(compressionQuality: 0.5)! }
var lowQualityJPEGNSData: Data { return self.jpegData(compressionQuality: 0.25)!}
var lowestQualityJPEGNSData: Data { return self.jpegData(compressionQuality: 0.0)! }
}
And you can call like this:
let imageData = imagePassed.lowestQualityJPEGNSData
In Swift
func ResizeImageFromOriginalSize(targetSize: CGSize) -> UIImage {
var actualHeight: Float = Float(self.size.height)
var actualWidth: Float = Float(self.size.width)
let maxHeight: Float = Float(targetSize.height)
let maxWidth: Float = Float(targetSize.width)
var imgRatio: Float = actualWidth / actualHeight
let maxRatio: Float = maxWidth / maxHeight
var compressionQuality: Float = 0.5
//50 percent compression
if actualHeight > maxHeight || actualWidth > maxWidth {
if imgRatio < maxRatio {
//adjust width according to maxHeight
imgRatio = maxHeight / actualHeight
actualWidth = imgRatio * actualWidth
actualHeight = maxHeight
}
else if imgRatio > maxRatio {
//adjust height according to maxWidth
imgRatio = maxWidth / actualWidth
actualHeight = imgRatio * actualHeight
actualWidth = maxWidth
}
else {
actualHeight = maxHeight
actualWidth = maxWidth
compressionQuality = 1.0
}
}
let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(actualWidth), height: CGFloat(actualHeight))
UIGraphicsBeginImageContextWithOptions(rect.size, false, CGFloat(compressionQuality))
self.draw(in: rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
Swift 3
#leo-dabus answer revised for swift 3
extension UIImage {
var uncompressedPNGData: Data? { return UIImagePNGRepresentation(self) }
var highestQualityJPEGNSData: Data? { return UIImageJPEGRepresentation(self, 1.0) }
var highQualityJPEGNSData: Data? { return UIImageJPEGRepresentation(self, 0.75) }
var mediumQualityJPEGNSData: Data? { return UIImageJPEGRepresentation(self, 0.5) }
var lowQualityJPEGNSData: Data? { return UIImageJPEGRepresentation(self, 0.25) }
var lowestQualityJPEGNSData:Data? { return UIImageJPEGRepresentation(self, 0.0) }
}
In Swift 4 I created this extension which will receive the expected size an try to reach it.
extension UIImage {
enum CompressImageErrors: Error {
case invalidExSize
case sizeImpossibleToReach
}
func compressImage(_ expectedSizeKb: Int, completion : (UIImage,CGFloat) -> Void ) throws {
let minimalCompressRate :CGFloat = 0.4 // min compressRate to be checked later
if expectedSizeKb == 0 {
throw CompressImageErrors.invalidExSize // if the size is equal to zero throws
}
let expectedSizeBytes = expectedSizeKb * 1024
let imageToBeHandled: UIImage = self
var actualHeight : CGFloat = self.size.height
var actualWidth : CGFloat = self.size.width
var maxHeight : CGFloat = 841 //A4 default size I'm thinking about a document
var maxWidth : CGFloat = 594
var imgRatio : CGFloat = actualWidth/actualHeight
let maxRatio : CGFloat = maxWidth/maxHeight
var compressionQuality : CGFloat = 1
var imageData:Data = UIImageJPEGRepresentation(imageToBeHandled, compressionQuality)!
while imageData.count > expectedSizeBytes {
if (actualHeight > maxHeight || actualWidth > maxWidth){
if(imgRatio < maxRatio){
imgRatio = maxHeight / actualHeight;
actualWidth = imgRatio * actualWidth;
actualHeight = maxHeight;
}
else if(imgRatio > maxRatio){
imgRatio = maxWidth / actualWidth;
actualHeight = imgRatio * actualHeight;
actualWidth = maxWidth;
}
else{
actualHeight = maxHeight;
actualWidth = maxWidth;
compressionQuality = 1;
}
}
let rect = CGRect(x: 0.0, y: 0.0, width: actualWidth, height: actualHeight)
UIGraphicsBeginImageContext(rect.size);
imageToBeHandled.draw(in: rect)
let img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if let imgData = UIImageJPEGRepresentation(img!, compressionQuality) {
if imgData.count > expectedSizeBytes {
if compressionQuality > minimalCompressRate {
compressionQuality -= 0.1
} else {
maxHeight = maxHeight * 0.9
maxWidth = maxWidth * 0.9
}
}
imageData = imgData
}
}
completion(UIImage(data: imageData)!, compressionQuality)
}
}
To Use
do {
try UiImageView.image?.compressImage(100, completion: { (image, compressRatio) in
print(image.size)
imageData = UIImageJPEGRepresentation(image, compressRatio)
base64data = imageData?.base64EncodedString()
})
} catch {
print("Error")
}
the most simpliest way i found to do it is
extension UIImage {
func compressImage(with maxSizeInBytes: Int ) -> UIImage? {
if maxSizeInBytes < 0 {
return nil
}
var currentImage:UIImage? = self
var divideQuality:CGFloat = 1.0
var imageData = self.jpegData(compressionQuality:divideQuality )
while imageData!.count > maxSizeInBytes {
divideQuality = divideQuality/2
imageData = currentImage?.jpegData(compressionQuality: divideQuality)
}
guard let data = imageData else {
return nil
}
currentImage = UIImage(data: data)
return UIImage(data: (currentImage?.jpegData(compressionQuality: divideQuality)) as! Data)
}
}

Resources