Optimize image file size (JPG, PNG) saved in Swift - ios

I have an image and I need to save it without loss of quality or resizing. The file size should be as small as possible. But I can't even save it with its original file size.
I have an image.jpg (file size 2.1Mb). I put it in bundle and read in a Swift code. After I extracted it to PNG with .pngData() - and save as .png the file size is 18.1 Mb - which is ridiculous, even if I remove the Alpha Channel - it still will have 9-12 Mb.
If I use .jpegData() - it is still much more than the source image
Only if I get jpeg with the quality 0.65% - I can have 2.1Mb - the same as the source but with visible loss in quality.
Where is the solution? How to optimize it and save without quality losses and without file size inflating?
func foo() {
let bundleURL = Bundle.main.bundleURL
// Source file size: 2.1 MB
let assetURL = bundleURL.appendingPathComponent("sourceBundle.bundle/image.jpg")
let srcUImage = UIImage.init(contentsOfFile: assetURL.relativePath)
if let dataPng = srcUImage!.pngData() {
printSize(data: dataPng) // Size: 18.1 MB
}
if let dataJpg = srcUImage!.jpegData(compressionQuality: 1.0) {
printSize(data: dataJpg) // Size: 5.3 MB
}
if let dataJpg = srcUImage!.jpegData(compressionQuality: 0.65) {
printSize(data: dataJpg) // Size: 2.1 MB
}
}
func printSize(data: Data) {
let bcf = ByteCountFormatter()
bcf.allowedUnits = [.useMB]
bcf.countStyle = .file
let string = bcf.string(fromByteCount: Int64(data.count))
print("Size: \(string)")
}

Related

Why app Terminates due to memory waring when convert pdf page to high quality image in ios, swift in real device

I'm trying to get the image(high quality) of each pdf page. I'm using below code running through a for loop until page count and it works.
guard let document = CGPDFDocument(pdfurl as CFURL) else { return }
guard let page = document.page(at: i) else { return }
let dpi: CGFloat = 300.0/72.0
let pagerect = page.getBoxRect(.mediaBox)
print(pagebounds)
print(pagerect)
let render = UIGraphicsImageRenderer(size: CGSize(width: pagerect.size.width * dpi, height: pagerect.size.height * dpi))
let imagedata = render.jpegData(withCompressionQuality: 0.5, actions: { cnv in
UIColor.white.set()
cnv.fill(pagerect)
cnv.cgContext.translateBy(x: 0.0, y: pagerect.size.height * dpi)
cnv.cgContext.scaleBy(x: dpi, y: -dpi)
cnv.cgContext.drawPDFPage(page)
})
let image = UIImage(data: imagedata)
I'm getting following issues with this ...
sometimes the image is nil.
When this runs, the usage of memory is very high.
With the page count(number of pages), usage of memory is very very high, and sometimes it goes to 1.4 GB and suddenly it crashes the app with the warning : Terminate due to memory waring . then I tried to run above code inside autoreleasepool. it did work but when the memory usage is more high (when it near to RAM size), again app crashes with above warning.
How can I avoid this memory warning and get the quality image form pdf page. hope any help. have a nice day.
If you are facing this issue then try this:
autoreleasepool {
guard let page = document.page(at: i) else { return }
// Fetch the page rect for the page we want to render.
let pageRect = page.getBoxRect(.mediaBox)
var dpi: CGFloat = 1.0
if pageRect.size.width > pageRect.size.height {
dpi = 3508.0 / pageRect.size.width
} else {
dpi = 3508.0 / pageRect.size.height
}
//dpi = 300
let format = UIGraphicsImageRendererFormat()
format.scale = 1
let renderer = UIGraphicsImageRenderer(size: CGSize(width: pageRect.size.width * dpi, height: pageRect.size.height * dpi), format: format)
let imagedata = renderer.jpegData(withCompressionQuality: 1.0, actions: { cnv in
UIColor.white.set()
cnv.fill(pageRect)
cnv.cgContext.translateBy(x: 0.0, y: pageRect.size.height * dpi)
cnv.cgContext.scaleBy(x: dpi, y: -dpi)
cnv.cgContext.drawPDFPage(page)
})
let image = UIImage(data: imagedata)
}
autoreleasepool - for permanent memory clearing
scale - so that images for different devices are not created, which increases their resolution by 2 or 3 times
changed the way to increase dpi as it can be initially more or less than 72
1) sometimes the image is nil.
Is there a reason that you are generating a jpeg data then converting to UIImage VS directly creating an uiimage (using func image(actions: (UIGraphicsImageRendererContext) -> Void) -> UIImage)?
If you really need to use the jpeg method, then don't directly instantiate from UIImage(data:), use CGImage's init?(jpegDataProviderSource source: CGDataProvider,
decode: UnsafePointer<CGFloat>?,
shouldInterpolate: Bool,
intent: CGColorRenderingIntent) then use UIImage(cgImage:) to get your UIImage instance
2) When this runs, the usage of memory is very high
Are you storing all images created per each page? If yes, then if you have a pdf of high number of pages then you will consume max memory at some point because of accumulated images. Why don't you store to disk each image created then releasing it afterwards so you don't accumulate the memory of storing all pages in memory.
Sharing the loop(assuming there is a loop) outside of this snippet could help solve your issue more

Get Image File Size from UIImage Property in Swift to validate Image Size

How can I get the File Size from UIImage Property in Swift?
My intention is to validate Images uploaded by the user. If it is above certain bytes or Size then I'll remove it otherwise allow the user to upload the file!
Thanks in Advance!
You can't get file size from UIImage, can get from the path where uploaded image stored
let filePath = "uploaded image url"
var fileSize : UInt64
do {
let attr = try FileManager.default.attributesOfItem(atPath: (filePath?.path)!)
fileSize = attr[FileAttributeKey.size] as! UInt64
print(fileSize)
let fileSizeWithUnit = ByteCountFormatter.string(fromByteCount: Int64(fileSize), countStyle: .file)
print("File Size: \(fileSizeWithUnit)")
} catch {
print("Error: \(error)")
}
Posting this as an answer, since you have a follow-up question. Since this is a bad UX, and your question is how to compress a UIImage in iOS, the answer is you can use UIImageJPEGRepresentation. and set this param:
compressionQuality
The quality of the resulting JPEG image, expressed as a value from 0.0
to 1.0. The value 0.0 represents the maximum compression (or lowest
quality) while the value 1.0 represents the least compression (or best
quality).
You could maybe have a cropping feature in your app.
Doc: https://developer.apple.com/documentation/uikit/1624115-uiimagejpegrepresentation

UIImageJPEGRepresentation vs UIImagePNGRepresentation for images in iOS

I am using UIImagePickerController to read images from the photo library. I use the following code to calculate their size:
if let file = info[UIImagePickerControllerOriginalImage] {
let imageValue = (file as? UIImage)!
let data = UIImageJPEGRepresentation(imageValue, 1)
let imageSize = (data?.count)! / 1024
print("imsize in MB: ", Double(imageSize) / 1024.0)
if let imageData = UIImagePNGRepresentation(imageValue) {
let bytes = imageData.count
let KB = Double(bytes) / 1024.0
let MB = Double(KB) / 1024.0
print("we have image size as MB", MB)
}
}
To my surprise both tell a different size for the images, which is also different from size of image. What is happening here and which is more accurate?
A bit confused. Help is much needed to understand this.
Jpeg and Png are different. Here I googled the diffrence between Jpeg and Png on google.
The main difference between JPG and PNG is the compression algorithms that they use. JPG uses a lossy compression algorithm that discards some of the image information in order to reduce the size of the file. ... With PNG, the quality of the image will not change, but the size of the file will usually be larger.

How do I reduce the quality of UIImage without reducing the resolution?

I have an upload limit of 2 MB with my images. So if a user tries to upload an image bigger then 2 MB, I would like to reduce it's size without reducing the resolution.
How do I achieve that? I tried something like this but it didn't work:
var fileSize = UIImageJPEGRepresentation(image, 1)!.length
print("before File size:")
print(fileSize)
while fileSize > MyConstants.MAX_ATTACHMENT_SIZE{
let mydata = UIImageJPEGRepresentation(image, 0.75)
fileSize = mydata!.length
image = UIImage(data: mydata!)!
print("make smaller \(fileSize)")
}
print("after File size:")
print(UIImageJPEGRepresentation(image, 1)!.length)
output:
before File size:
2298429
make smaller 846683
after File size:
2737491
As #Lion said, you will have to play around with the quality to achieve an agreeable file size. I noticed however that:
print(UIImageJPEGRepresentation(image, 1)!.length)
Will print the image length at max quality. This is misleading since inside the while condition, you are achieving smaller file size.
while fileSize > MyConstants.MAX_ATTACHMENT_SIZE{
let mydata = UIImageJPEGRepresentation(image, 0.75)
fileSize = mydata!.length
image = UIImage(data: mydata!)!
print("make smaller \(fileSize)")
}
let mydata = UIImageJPEGRepresentation(image, 0.75)
fileSize = mydata!.length
image = UIImage(data: mydata!)!
This is the right approach. You can set different scale from 0.1 to 1 that how much you want to reduce the quality and size of the image.
print(UIImageJPEGRepresentation(image, 1)!.length) this line again increase image's quality and size or resolution to maximum (because scale = 1)

How can I set DPI info of a JPG in iOS (iPhone/iPad) without changing the image dimensions? (Objective-C)

Is there some way to just set the DPI information of a JPG file without changing its width and height in iOS?
Example: We have a JPG with 2652 x 1850 with 72 dpi (IOS Default) and want to create a JPG with the same size (2652x1850) with 300 dpi (just to have a print size more reasonable). Any suggestions will be welcome. Important: we need this in an IPAD/IPHONE application
Here is a Data extension in Swift:
extension Data {
mutating func setDpi(_ dpi: Int) {
let firstPart = UInt8(UInt16(dpi) >> 8)
let lastPart = UInt8(UInt16(dpi) & 0xff)
self[13] = 1
self[14] = firstPart
self[15] = lastPart
self[16] = firstPart
self[17] = lastPart
}
}
Usage:
let image = UIImage(imageLiteralResourceName: "example")
var imageData = image.jpegData(compressionQuality: 0.9)
imageData?.setDpi(300)

Resources