PFFile olarak Ayrıştır'a yüklemeden önce bir resmin boyutunu küçültmek veya küçültmek nasıl sıkıştırılır? (Swift)


88

Doğrudan telefonda fotoğraf çektikten sonra Ayrıştır'a bir resim dosyası yüklemeye çalışıyordum. Ancak bir istisna yaratır:

Yakalanmamış istisna 'NSInvalidArgumentException' nedeniyle uygulama sonlandırılıyor, neden: 'PFFile 10485760 bayttan büyük olamaz'

İşte kodum:

İlk görünüm denetleyicisinde:

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

Görüntüyü yükleyen görünüm denetleyicisinde:

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

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

Ama yine de o fotoğrafı Parse'a yüklemem gerekiyor. Görüntünün boyutunu veya çözünürlüğünü azaltmanın bir yolu var mı?


Son olarak gbk ve fount'un son önermesini denedim, eğer let newData = UIImageJPEGRepresentation (UIImage (data: data), 1) newData.count, data.count'a eşit değil ve çok daha büyük bir factore ile gerçekten daha büyük. 2. Hangisi benim için gerçekten şaşırtıcı! Her neyse, kod için teşekkürler!
NicoD

Yanıtlar:


188

Evet , resim dosyanızın boyutunu küçültmek UIImageJPEGRepresentationyerine kullanabilirsiniz UIImagePNGRepresentation. Aşağıdaki gibi bir UIImage uzantısı oluşturabilirsiniz:

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)
    }
}

düzenle / güncelle:

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)
    }
}

Kullanım:

if let imageData = image.jpeg(.lowest) {
    print(imageData.count)
}

1
Bildirilmemiş UIImage tipi kullanımı.
Aldığım

1
22-25MB boyutlarında görüntüler döndürüyordum, şimdi bunun bir kısmı. Çok teşekkür ederim! Harika uzantı!
Octavio Antonio Cedeño

3
Sözdizimi dışında hiçbir fark yoktur. Elbette UIImageJPEGRepresentation(yourImage, 1.0)sadece yazmak yerine kodu manuel olarak yazabilir .jpve xcode'un yöntemi sizin için otomatik olarak tamamlamasına izin verebilirsiniz . aynı sıkıştırma numaralandırması için .whatever.
Leo Dabus

1
@Umitk, UIKit'i içe aktarmalısınız
Alan

1
'jpegData (sıkıştırma Kalitesi :)', 'UIImageJPEGRepresentation ( : :)' olarak yeniden adlandırıldı
5uper_0leh

53

Görüntünün boyutunu somut bir değerle sınırlamak istiyorsanız, şunları yapabilirsiniz:

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
    } 
}

Bu daha kapsamlı bir çözümdür.
Idrees Ashraf

swift 3.1if let data = bits.representation(using: .jpeg, properties: [.compressionFactor:compressingValue])
Jacksonsox

16
Bu çok pahalıdır, bir süre döngüsünde ve her seferinde bu kadar ağır bir görev yapıyorsunuz !! sınırlayıcı koşul yok ..
Amber K

Kapsamlı ama hafızası gerçekten zor. Bu, bellek sorunları olan eski cihazların çökmesine neden olur. Bunu yapmanın daha ucuz bir yolu olmalı.
Tofu Warrior

1
Bu gerçekten kötü bir fikir, çok kötü durumlarla dolu. 1) 1.0'ın sıkıştırılmasıyla başlar, bu da neredeyse hiç sıkıştırma anlamına gelmez. Görüntü boyutları küçükse, görüntüler ihtiyaç duyduklarından çok daha fazla KB olacaktır. 2) Görüntüler büyükse, hedef boyutun altına inmek için birçok kez yeniden sıkıştırılabileceği için yavaş olacaktır. 3) Görüntüler çok büyükse, görüntünün çöp gibi göründüğü noktaya kadar sıkıştırılabilir. Bu gibi durumlarda, görüntüyü kaydetmekte başarısız olmak ve kullanıcıya çok büyük olduğunu söylemek daha iyi olacaktır.
Orion Edwards

10
  //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!)!
}

9

Xcode 7 için Jus Fixing, 21.09.2015 tarihinde test edildi ve iyi çalışıyor:

Aşağıdaki UIImagegibi bir uzantı oluşturun :

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)! }
}

O zaman bunu şu şekilde kullanabilirsiniz:

let imageData = imagePassed.lowestQualityJPEGNSData

Cevap sahibi Mr. Thiago & editörü Bay kuldeep için Çekirdek sayesinde
Abhimanyu Rathore

5

Swift 4 İkili YaklaşımGörüntüyü sıkıştırmak için

Bu soruyu cevaplamak için oldukça geç olduğuna inanıyorum ama işte optimize edilmiş soruya çözümüm İkili arama kullanıyorum , optimum değeri bulmak için . Bu nedenle, örneğin, normal çıkarma yaklaşımı ile% 62'ye ulaşmak için 38 sıkıştırma denemesi gerektiğini, * İkili arama ** yaklaşımı maksimum günlük (100) = yaklaşık 7 girişimde gerekli çözüme ulaşacaktır.

Bununla birlikte, UIImageJPEGRepresentationözellikle sayı 1'e yaklaştığında işlevin doğrusal olarak davranmadığını da size bildirmek isterim . Float değeri> 0.995 olduktan sonra görüntünün sıkıştırmanın durduğunu görebileceğimiz ekran yakalama ekranı. Davranış oldukça öngörülemez, bu nedenle bu tür durumları ele alacak bir delta tamponuna sahip olmak daha iyidir.

görüntü açıklamasını buraya girin

İşte bunun kodu

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)!
    }
}

Neden bir While döngüsü kullanıyorsunuz ve bir değişkeni manuel olarak artırıyorsunuz? Sadece kullan for i in 0...13.
Peter Schorn

3

UIImageUzatma ile bu çok basit

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)!)
    }
}

kullanmak

// max 300kb
image.resizeByByte(maxByte: 300000) { (resizedData) in
    print("image size: \(resizedData.count)")
}

4
çok yavaş ve senkronize
iman kazemayni

1
yeniden boyutlandırılan görüntünüz asla 300kB'den daha az olmayacak ve bu durum için herhangi bir geri
dönüşünüz olmayabilir

en azından (compressQuality> = 0) while döngüsüne ekstra && koşulu olarak eklenebilir
slxl

2

Swift 4.2 güncellemesi . UIImage boyutunu azaltmak için bu uzantıyı oluşturdum.
Burada iki yönteminiz var, ilki bir yüzde alıyor ve ikincisi görüntüyü 1MB'ye düşürüyor.
Elbette ikinci yöntemi 1KB veya istediğiniz herhangi bir boyut olacak şekilde değiştirebilirsiniz.

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
    }
}

2

Swift 5'te @Thiago Arreguy yanıtı olarak:

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)! }

}

Ve şöyle arayabilirsin:

let imageData = imagePassed.lowestQualityJPEGNSData

2

Swift'de

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!
    }

2

func jpegData(compressionQuality: CGFloat) -> Data?Belirli bir boyuta sıkıştırmanız gerekmiyorsa kullanmak iyi çalışır. Bununla birlikte, belirli görüntüler için, belirli bir dosya boyutunun altında sıkıştırabilmeyi yararlı buluyorum. Bu durumda,jpegData güvenilmezdir ve bir görüntünün yinelemeli sıkıştırılması dosya boyutunda düzlüğe neden olur (ve gerçekten pahalı olabilir). Bunun yerine, UIImage'ın boyutunu küçültmeyi, ardından jpegData'ya dönüştürmeyi ve küçültülmüş boyutun seçtiğim değerin altında olup olmadığını kontrol etmeyi tercih ediyorum (belirlediğim bir hata payı dahilinde). En pahalı olan ilk yinelemeleri hızlandırmak için sıkıştırma adımı çarpanını mevcut dosya boyutunun istenen dosya boyutuna oranına göre ayarlıyorum.

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()
    }
}

Ve kullanım:

let data = image.compress(to: 300)

UIImage resizeduzantısı şuradan gelir: Yükleme resmi boyutunu azaltmak için UIImage'ı nasıl yeniden boyutlandırırım


1

Hızlı 3

@ leo-dabus cevabı swift 3 için revize edildi

    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)  }
}

1

Swift 4'te beklenen boyutu alacak ve ona ulaşmaya çalışacak bu uzantıyı oluşturdum.

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)
    }


}

Kullanmak

        do {
            try UiImageView.image?.compressImage(100, completion: { (image, compressRatio) in
                print(image.size) 
                imageData = UIImageJPEGRepresentation(image, compressRatio)
                base64data = imageData?.base64EncodedString()

            })
        } catch {
                 print("Error")
        }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.