I've an image (captureImage) which is taken by camera and resized. I use UIImageJPEGRepresentation to compress image, after the compression, image's size is increased. Is it normal behaviour or I'm doing compression/resizing in wrong way?
if let image = captureImage {
print(image.size) // (700, 700)
let compressedImageData = UIImageJPEGRepresentation(image, 0.3)!
let compressedImage = UIImage(data: compressedImageData)!
print(compressedImage.size) // (3024, 3024)
}
Yes this code is for image compression
let compressedImageData = UIImageJPEGRepresentation(image, 0.3)!
One way for getting the file size is by saving into document directory.
For example -
let filePath = fileURL
var fileSize : UInt64
do {
//return [FileAttributeKey : Any]
let attr = try FileManager.default.attributesOfItem(atPath: filePath.path)
fileSize = attr[FileAttributeKey.size] as! UInt64
//if you convert to NSDictionary, you can get file size old way as well.
let dict = attr as NSDictionary
fileSize = dict.fileSize()
print(fileSize)
} catch {
print("Error: \(error)")
}
Related
I am trying to convert the Image selected from the UIImagePickerController into base64 String. But the length of the String is about more than 12 Corerit's making iPhone hang.
This is the code that I using.
func compressImage(img:UIImage) -> String {
Utill.showProgress()
var imageData = Data(UIImagePNGRepresentation(img)! )
print("***** Uncompressed Size \(imageData.description) **** ")
imageData = UIImageJPEGRepresentation(img, 0.025)!
print("***** Compressed Size \(imageData.description) **** ")
let image = UIImage(data: imageData)
let imagesData:NSData = UIImagePNGRepresentation(image!)! as NSData
let strBase64 = imagesData.base64EncodedString(options: .lineLength64Characters)
Utill.dismissProgress()
return strBase64
}
Is there any other way to reduce the String, to be around 10K - 30K?
Try this code,
func convertImageToBase64(image: UIImage)-> String {
if let imageData = image.jpegData(compressionQuality: 0.25){
let base64String = imageData.base64EncodedString()
return base64String
}
return ""
}
When I load an image from a file path into a UIImage, the size ends up increasing. Am I doing something wrong?
let imageData = try! Data(contentsOf: imageUrl)
print("Original file size : \(imageData.count.readableSize())") // Displays 2.34 MB
let image = UIImage(contentsOfFile: imageUrl.path)
print("UIImage file size : \(image.pngData()!.readableSize())") // Displays 5.71 MB
extension Int
{
func readableSize() -> String
{
let units = ["bytes", "KB", "MB", "GB", "TB"]
var count = 0
var readableFileSize = self
while readableFileSize > 1024
{
readableFileSize = readableFileSize/1024
count += 1
}
return String(format: "%.2f", readableFileSize) + " " + units[count]
}
}
In the above code sample, assume that the image loaded is of png format.
I am looking for a way to circumvent this and get the actual data that was initially loaded
Edit: I'm trying to load an image into UIImage, resize it and then save it back into the file system. I'm looking for a way to achieve this without increasing the size of the file because of what I have described. I'm open to even an alternative to resize without loading the image into UIImage.
Maybe it does not answer your question but I want to share some useful information.
Code:
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileName = "2.png"
let fileURL = documentsDirectory.appendingPathComponent(fileName)
let imageData = try! Data(contentsOf: fileURL)
print("Original file size : \(imageData.count/1024)")
let image = UIImage(contentsOfFile: fileURL.path)
print("UIImage file size : \(image!.pngData()!.count/1024)")
Debug log as you mentioned in your question(size is different):
Original file size : 6083
UIImage file size : 14982
Conclusion:
After lot's of research and frustration, I realized that the size of
an image in document directory (14982) and size image from data is
identical (14982) that's mean when we are getting data from URL automatically compress it by the system.
Try this:
let remoteImageData = try? Data(contentsOf: URL(string: "https://upload.wikimedia.org/wikipedia/de/b/bb/Png-logo.png")!)
print(remoteImageData)
if let imageFileUrl = Bundle.main.url(forResource: "Png-logo", withExtension: "png") {
let imageFileData = try? Data(contentsOf: imageFileUrl)
print(imageFileData)
}
Prints:
Optional(811068 bytes)
Optional(811068 bytes)
I'm saving an image picked from Photos in a Document folder.
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let selectedImage = info[.originalImage] as? UIImage else {
fatalError("Expected a dictionary containing an image, but was provided the following: \(info)")
}
selectedImage.jpegData(compressionQuality: 0.0).fileSize() //Printing size as 4284217 bytes|4.3 MB
if let imageData = selectedImage.jpeg(.lowest) {
imageData.fileSize() //Printing size as 4284217 bytes|4.3 MB
let compressedImage = UIImage(data: imageData)?.fixOrientation()
saveImageInDocuments(compressedImage!)
}
picker.dismiss(animated: true, completion: nil)
}
After selecting the image and compressing to minimum size, I have printed the size of Image. Next step is to save Image to Documents Folder.
func saveImageInDocuments(_ image: UIImage) {
let imageName = String(Date().toMillis())
let fileURL = documentsDirectoryURL.appendingPathComponent("\(CUST_APP_DOC)/\(imageName).png")
print(fileURL.path)
if !FileManager.default.fileExists(atPath: fileURL.path) {
do {
try image.pngData()!.write(to: fileURL)
print("Saved filename: \(imageName).png size: \(calculateFileSize(path: String(describing: fileURL)).0)")
} catch {
print(error)
}
} else {
print("Image Not Added")
}
}
Methods used to get size of the image and calculate the size of Image saved at path
func calculateFileSize(path: String) -> (String, Double) {
let absolutePath = path.replacingOccurrences(of: "file://", with: "")
let fileAttributes = try! FileManager.default.attributesOfItem(atPath: absolutePath)
let fileSizeNumber = fileAttributes[FileAttributeKey.size] as! NSNumber
let fileSize = fileSizeNumber.int64Value
var sizeMB = Double(fileSize / 1024)
sizeMB = Double(sizeMB / 1024)
print(String(format: "%.2f", sizeMB) + " MB")
return (String(format: "%.2f", sizeMB) + " MB", sizeMB)
}
extension Data {
func fileSize() {
let bcf = ByteCountFormatter()
bcf.allowedUnits = [.useMB] // optional: restricts the units to MB only
bcf.countStyle = .file
let string = bcf.string(fromByteCount: Int64(self.count))
print("Printing size as \(self.count) bytes|\(string) MB")
}
}
The issue is that after selecting the image the image file size is 4.3 MB but while after saving the image in folder. The size of image is coming "Saved filename: 1546593850872.png size: 29.86 MB"
Why is this happening? If anyone can help to explain this, I will be thankful to the person.
After searching for this whole day, I found the answer to this scenario.
In didFinishPickingMediaWithInfo I was compressing the image to JPEG where the image size was getting reduced. Then while writing the image to directory saveImageInDocuments, I was converting the JPEG image again to PNG using this line of code.
try image.pngData()!.write(to: fileURL)
Converting the image to PNG was increasing its size.
For solution I have modified this method:
func saveImageInDocuments(_ imageData: Data) {
let imageName = String(Date().toMillis())
let fileURL = documentsDirectoryURL.appendingPathComponent("\(CUST_APP_DOC)/\(imageName).png")
print(fileURL.path)
if !FileManager.default.fileExists(atPath: fileURL.path) {
do {
try imageData.write(to: fileURL)
} catch {
print(error)
}
} else {
print("Image Not Added")
}
}
Directly saved the compressed JPEG image.
I need to convert the image to/from Base64.
All working fine for JPG files, but if I upload PNG and then open it in the app it leads to the crash with error
"Unexpectedly found nil while unwrapping an Optional value"
while trying to create Data from the encoded string
Here is my code:
For Encoding:
static func base64Convert(base64String: String?) -> UIImage {
var decodedImage = #imageLiteral(resourceName: "no_prof_image")
if ((base64String?.isEmpty)! || (base64String?.contains("null"))!) {
return decodedImage
}else {
if let imageBase64String = base64String,
let dataDecoded = Data(base64Encoded: imageBase64String, options: .ignoreUnknownCharacters) {
decodedImage = UIImage(data: dataDecoded) ?? #imageLiteral(resourceName: "no_prof_image")
}
return decodedImage
}
}
For Decoding:
static func makeProfileBase64FromImage(image: UIImage) -> String? {
var imageData : Data?
if let jpegData = UIImageJPEGRepresentation(image, 1.0) {
imageData = jpegData
} else if let pngData = UIImagePNGRepresentation(image) {
imageData = pngData
}
return imageData?.base64EncodedString()
}
What I tried:
1) All encoding options
2) All decoding options
3) Swap UIImageJPEGRepresentation to UIImagePNGRepresentation. It leads to the same error but with jpg images.
UPDATE
Here is code how I send data to the server:
var imageToSend : String = "null"
if profileImage.image != #imageLiteral(resourceName: "no_prof_image"),
let validImage = profileImage.image,
let imageBase64 = AppUtils.makeProfileBase64FromImage(image: validImage) {
imageToSend = imageBase64
}
let parameters : Parameters = [
"image": imageToSend
]
Alamofire.request(AppUtils.API_URL + "update_profile.php", method: .post, parameters: parameters)
.validate().responseData() {response in
switch response.result {
case .success:
//...Some stuff
break
case .failure:
//...Some stuff
break
}
}
Part of the string that came to the server:
/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAABkKADAAQAAAABAAABkAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8AAEQgBkAGQAwERAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//E
UPDATED CODE IN THE QUESTION
For now, the code doesn't have force unwrap. But now I always gets standard #imageLiteral(resourceName: "no_prof_image"). (Before, at least jpg works fine :) )
Quite obviously, you use UIImageJPEGRepresentation for .jpeg images, but for .png images you should use UIImagePNGRepresentation
Also, don't use force unwrapping.
static func makeBase64FromImage(image: UIImage) -> String? {
var imageData : Data?
if let jpegData = UIImageJPEGRepresentation(image, 1.0) {
imageData = jpegData
} else if let pngData = UIImagePNGRepresentation(image) {
imageData = pngData
}
return imageData?.base64EncodedString()
}
Looks like your issue is your PNG data size which is much bigger than JPEG data. So your server might have a size limit for your image upload.
Regarding your encoding method The second condition else if let pngData = UIImagePNGRepresentation(image) will never be executed. You have to choose which one you would like to use PNG or JPEG data representations (JPEG most times due to the size limit). Btw this would be much easier using optional chaining.
return UIImageJPEGRepresentation(image, 1)?.base64EncodedString()
Swift 4.2 Xcode 10 or later
return image.jpegData(compressionQuality: 1)?.base64EncodedString()
As #mag_zbc suggested, start with:
static func makeBase64FromImage(image: UIImage) -> String? {
var imageData : Data?
if let jpegData = UIImageJPEGRepresentation(image, 1.0) {
imageData = jpegData
} else if let pngData = UIImagePNGRepresentation(image) {
imageData = pngData
}
return imageData?.base64EncodedString()
}
Then, update this code to:
var imageToSend : String = "null"
if profileImage.image != #imageLiteral(resourceName: "no_prof_image"),
let validImage = profileImage.image,
let imageBase64 = AppUtils.makeBase64FromImage(image: validImage) {
imageToSend = imageBase64
}
let parameters : Parameters = [
"image": imageToSend
]
...
In general, you want to avoid using "!" anywhere unless you can 100% confirm that in any and all cases the value will always be defined. In this case, I believe the issue was your code being called with profileImage.image == nil
A profileImage.image being nil would != to the image literal, and therefore would have entered the conditional if you defined. Then by forcing it to be unwrapped with "!" you tried to unwrap nil.
Good luck!
I'm trying to save a UIImage to NSData and then read the NSData back to a new UIImage in Swift. To convert the UIImage to NSData I'm using the following code:
let imageData: NSData = UIImagePNGRepresentation(myImage)
How do I convert imageData (i.e., NSData) back to a new UIImage?
UIImage(data:imageData,scale:1.0) presuming the image's scale is 1.
In swift 4.2, use below code for get Data().
image.pngData()
Thanks. Helped me a lot. Converted to Swift 3 and worked
To save: let data = UIImagePNGRepresentation(image)
To load: let image = UIImage(data: data)
Use imageWithData: method, which gets translated to Swift as UIImage(data:)
let image : UIImage = UIImage(data: imageData)
Now in Swift 4.2 you can use pngData() new instance method of UIImage to get the data from the image
let profileImage = UIImage(named:"profile")!
let imageData = profileImage.pngData()
Details
Xcode 10.2.1 (10E1001), Swift 5
Solution 1
guard let image = UIImage(named: "img") else { return }
let jpegData = image.jpegData(compressionQuality: 1.0)
let pngData = image.pngData()
Solution 2.1
extension UIImage {
func toData (options: NSDictionary, type: CFString) -> Data? {
guard let cgImage = cgImage else { return nil }
return autoreleasepool { () -> Data? in
let data = NSMutableData()
guard let imageDestination = CGImageDestinationCreateWithData(data as CFMutableData, type, 1, nil) else { return nil }
CGImageDestinationAddImage(imageDestination, cgImage, options)
CGImageDestinationFinalize(imageDestination)
return data as Data
}
}
}
Usage of solution 2.1
// about properties: https://developer.apple.com/documentation/imageio/1464962-cgimagedestinationaddimage
let options: NSDictionary = [
kCGImagePropertyOrientation: 6,
kCGImagePropertyHasAlpha: true,
kCGImageDestinationLossyCompressionQuality: 0.5
]
// https://developer.apple.com/documentation/mobilecoreservices/uttype/uti_image_content_types
guard let data = image.toData(options: options, type: kUTTypeJPEG) else { return }
let size = CGFloat(data.count)/1000.0/1024.0
print("\(size) mb")
Solution 2.2
extension UIImage {
func toJpegData (compressionQuality: CGFloat, hasAlpha: Bool = true, orientation: Int = 6) -> Data? {
guard cgImage != nil else { return nil }
let options: NSDictionary = [
kCGImagePropertyOrientation: orientation,
kCGImagePropertyHasAlpha: hasAlpha,
kCGImageDestinationLossyCompressionQuality: compressionQuality
]
return toData(options: options, type: .jpeg)
}
func toData (options: NSDictionary, type: ImageType) -> Data? {
guard cgImage != nil else { return nil }
return toData(options: options, type: type.value)
}
// about properties: https://developer.apple.com/documentation/imageio/1464962-cgimagedestinationaddimage
func toData (options: NSDictionary, type: CFString) -> Data? {
guard let cgImage = cgImage else { return nil }
return autoreleasepool { () -> Data? in
let data = NSMutableData()
guard let imageDestination = CGImageDestinationCreateWithData(data as CFMutableData, type, 1, nil) else { return nil }
CGImageDestinationAddImage(imageDestination, cgImage, options)
CGImageDestinationFinalize(imageDestination)
return data as Data
}
}
// https://developer.apple.com/documentation/mobilecoreservices/uttype/uti_image_content_types
enum ImageType {
case image // abstract image data
case jpeg // JPEG image
case jpeg2000 // JPEG-2000 image
case tiff // TIFF image
case pict // Quickdraw PICT format
case gif // GIF image
case png // PNG image
case quickTimeImage // QuickTime image format (OSType 'qtif')
case appleICNS // Apple icon data
case bmp // Windows bitmap
case ico // Windows icon data
case rawImage // base type for raw image data (.raw)
case scalableVectorGraphics // SVG image
case livePhoto // Live Photo
var value: CFString {
switch self {
case .image: return kUTTypeImage
case .jpeg: return kUTTypeJPEG
case .jpeg2000: return kUTTypeJPEG2000
case .tiff: return kUTTypeTIFF
case .pict: return kUTTypePICT
case .gif: return kUTTypeGIF
case .png: return kUTTypePNG
case .quickTimeImage: return kUTTypeQuickTimeImage
case .appleICNS: return kUTTypeAppleICNS
case .bmp: return kUTTypeBMP
case .ico: return kUTTypeICO
case .rawImage: return kUTTypeRawImage
case .scalableVectorGraphics: return kUTTypeScalableVectorGraphics
case .livePhoto: return kUTTypeLivePhoto
}
}
}
}
Usage of solution 2.2
let compressionQuality: CGFloat = 0.4
guard let data = image.toJpegData(compressionQuality: compressionQuality) else { return }
printSize(of: data)
let options: NSDictionary = [
kCGImagePropertyHasAlpha: true,
kCGImageDestinationLossyCompressionQuality: compressionQuality
]
guard let data2 = image.toData(options: options, type: .png) else { return }
printSize(of: data2)
Problems
Image representing will take a lot of cpu and memory resources. So, in this case it is better to follow several rules:
- do not run jpegData(compressionQuality:) on main queue
- run only one jpegData(compressionQuality:) simultaneously
Wrong:
for i in 0...50 {
DispatchQueue.global(qos: .utility).async {
let quality = 0.02 * CGFloat(i)
//let data = image.toJpegData(compressionQuality: quality)
let data = image.jpegData(compressionQuality: quality)
let size = CGFloat(data!.count)/1000.0/1024.0
print("\(i), quality: \(quality), \(size.rounded()) mb")
}
}
Right:
let serialQueue = DispatchQueue(label: "queue", qos: .utility, attributes: [], autoreleaseFrequency: .workItem, target: nil)
for i in 0...50 {
serialQueue.async {
let quality = 0.02 * CGFloat(i)
//let data = image.toJpegData(compressionQuality: quality)
let data = image.jpegData(compressionQuality: quality)
let size = CGFloat(data!.count)/1000.0/1024.0
print("\(i), quality: \(quality), \(size.rounded()) mb")
}
}
Links
UTI Image Content Types
CGImageDestinationAddImage(::_:)
Thinking about Memory: Converting UIImage to Data in Swift
Different resize technics
To save as data:
From StoryBoard, if you want to save "image" data on the imageView of MainStoryBoard, following codes will work.
let image = UIImagePNGRepresentation(imageView.image!) as NSData?
To load "image" to imageView:
Look at exclamation point "!", "?" closely whether that is quite same as this one.
imageView.image = UIImage(data: image as! Data)
"NSData" type is converted into "Data" type automatically during this process.
Image to Data:-
if let img = UIImage(named: "xxx.png") {
let pngdata = img.pngData()
}
if let img = UIImage(named: "xxx.jpeg") {
let jpegdata = img.jpegData(compressionQuality: 1)
}
Data to Image:-
guard let image = UIImage(data: pngData) else { return }
For safe execution of code, use if-let block with Data to prevent app crash & , as function UIImagePNGRepresentation returns an optional value.
if let img = UIImage(named: "TestImage.png") {
if let data:Data = UIImagePNGRepresentation(img) {
// Handle operations with data here...
}
}
Note: Data is Swift 3+ class. Use Data instead of NSData with
Swift 3+
Generic image operations (like png & jpg both):
if let img = UIImage(named: "TestImage.png") { //UIImage(named: "TestImage.jpg")
if let data:Data = UIImagePNGRepresentation(img) {
handleOperationWithData(data: data)
} else if let data:Data = UIImageJPEGRepresentation(img, 1.0) {
handleOperationWithData(data: data)
}
}
*******
func handleOperationWithData(data: Data) {
// Handle operations with data here...
if let image = UIImage(data: data) {
// Use image...
}
}
By using extension:
extension UIImage {
var pngRepresentationData: Data? {
return UIImagePNGRepresentation(self)
}
var jpegRepresentationData: Data? {
return UIImageJPEGRepresentation(self, 1.0)
}
}
*******
if let img = UIImage(named: "TestImage.png") { //UIImage(named: "TestImage.jpg")
if let data = img.pngRepresentationData {
handleOperationWithData(data: data)
} else if let data = img.jpegRepresentationData {
handleOperationWithData(data: data)
}
}
*******
func handleOperationWithData(data: Data) {
// Handle operations with data here...
if let image = UIImage(data: data) {
// Use image...
}
}
Swift 5
let the image you create as UIImage be image
image.pngData() as NSData?
Use this for a simple solution
static var UserProfilePhoto = UIImage()
guard let image = UIImage(named: "Photo") else { return }
guard let pngdata = image.pngData() else { return }
UserProfilePhoto = UIImage(data: pngdata)!