Как уменьшить размер изображения перед его загрузкой в Parse как PFFile? (Swift)
Я пытался загрузить файл изображения в Parse после того, как сделал фотографию прямо на телефон. Но это исключение:
Завершение работы приложения из-за необработанного исключения "NSInvalidArgumentException", причина: "PFFile не может быть больше 10485760 байт"
Вот мой код:
В первом представлении контроллер:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "getImage")
{
var svc = segue.destinationViewController as! ClothesDetail
svc.imagePassed = imageView.image
}
}
В контроллере представления, который загружает изображение:
let imageData = UIImagePNGRepresentation(imagePassed)
let imageFile = PFFile(name: "\(picName).png", data: imageData)
var userpic = PFObject(className:"UserPic")
userpic["picImage"] = imageFile`
Но мне все еще нужно загрузить это фото в Parse. Есть ли способ уменьшить размер или разрешение изображения?
14 ответов
Да, вы можете использовать UIImageJPEGRepresentation
вместо 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)
}
}
редактировать / обновление:
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)
}
}
Использование:
if let imageData = image.jpeg(.lowest) {
print(imageData.count)
}
Если вы хотите ограничить размер изображения каким-либо конкретным значением, вы можете сделать следующее:
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
}
}
С помощью func jpegData(compressionQuality: CGFloat) -> Data?
хорошо работает, если вам не нужно сжимать до определенного размера. Однако для некоторых изображений я считаю полезным иметь возможность сжимать файл ниже определенного размера. В этом случае,jpegData
является ненадежным, а итеративное сжатие изображения приводит к выходу на плато в размере файла (и может быть очень дорогостоящим). Вместо этого я предпочитаю уменьшить размер самого UIImage, затем преобразовать его в jpegData и проверить, не меньше ли уменьшенный размер выбранному мной значению (в пределах установленной мной погрешности). Я регулирую множитель шага сжатия на основе отношения текущего размера файла к желаемому, чтобы ускорить первые итерации, которые являются самыми дорогими.
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()
}
}
И использование:
let data = image.compress(to: 300)
UIImage resized
расширение происходит из: Как изменить размер UIImage, чтобы уменьшить размер загружаемого изображения
//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 для Xcode 7, протестирован 21.09.2015 и работает нормально:
Просто создайте расширение UIImage
следующим образом:
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)! }
}
Тогда вы можете использовать это так:
let imageData = imagePassed.lowestQualityJPEGNSData
Swift 4 бинарный подход для сжатия изображения
Я полагаю, что уже довольно поздно, чтобы ответить на этот вопрос, но вот мое решение оптимизированного вопроса. Я использую бинарный поиск, чтобы найти оптимальное значение. Так, например, скажем, что при обычном подходе вычитания для достижения 62% потребуется 38 попыток сжатия, подход * Бинарный поиск ** достигнет требуемого решения в max log(100) = около 7 попыток.
Тем не менее, также хотел бы сообщить вам, что UIImageJPEGRepresentation
Функция не ведет себя линейно, особенно когда число достигает значения, близкого к 1. Вот снимок экрана, на котором мы видим, что изображение перестает сжиматься после того, как значение с плавающей запятой> 0,995. Поведение довольно непредсказуемо, поэтому лучше иметь дельта-буфер, который бы обрабатывал такие случаи.
Вот код для этого
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)!
}
}
Это очень просто с UIImage
расширение
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)!)
}
}
использовать
// max 300kb
image.resizeByByte(maxByte: 300000) { (resizedData) in
print("image size: \(resizedData.count)")
}
Обновление Swift 4.2. Я создал это расширение, чтобы уменьшить размер UIImage.
Здесь у вас есть два метода: первый берет процент, а второй уменьшает изображение до 1 МБ.
Конечно, вы можете изменить второй метод, чтобы он стал 1 КБ или любым другим размером.
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
}
}
В Свифте
func ResizeImageOriginalSize(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 5 как ответ @Thiago Arreguy:
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)! }
}
А позвонить можно так:
let imageData = imagePassed.lowestQualityJPEGNSData
Свифт 3
@ leo-dabus ответ исправлен для 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) }
}
В Swift 4 я создал это расширение, которое получит ожидаемый размер, пытаясь достичь его.
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)
}
}
Использовать
do {
try UiImageView.image?.compressImage(100, completion: { (image, compressRatio) in
print(image.size)
imageData = UIImageJPEGRepresentation(image, compressRatio)
base64data = imageData?.base64EncodedString()
})
} catch {
print("Error")
}
Самый простой способ, который я нашел, - это
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)
}
}
Это самый короткий способ сделать это:
func compressToMaxMB(_ maxMB: Double) -> Data? {
let currentSize = Double(self.jpegData(compressionQuality: 1)?.count ?? 0)
let quality: CGFloat = (maxMB * 1000000) / currentSize
return self.jpegData(compressionQuality: quality)
}