Bagaimana cara mengompres pengurangan ukuran gambar sebelum mengunggah ke Parse as PFFile? (Cepat)

88

Saya mencoba mengunggah file gambar ke Parse setelah mengambil foto langsung di ponsel. Tapi itu membuat pengecualian:

Menghentikan aplikasi karena pengecualian yang tidak tertangkap 'NSInvalidArgumentException', alasan: 'PFFile tidak boleh lebih besar dari 10485760 byte'

Ini kode saya:

Pada pengontrol tampilan pertama:

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

Dalam pengontrol tampilan yang mengunggah gambar:

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

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

Tapi saya masih perlu mengunggah foto itu ke Parse. Adakah cara untuk mengurangi ukuran atau resolusi gambar?

elvislkm
sumber
Saya mencoba proposisi terakhir gbk dan fount pada akhirnya bahwa jika saya memanggil let newData = UIImageJPEGRepresentation (UIImage (data: data), 1) newData.count tidak sama dengan data.count dan benar-benar lebih besar dengan faktor lebih dari 2. Yang bagi saya sangat mengejutkan! Bagaimanapun, terima kasih untuk kodenya!
NicoD

Jawaban:

188

Ya, Anda dapat menggunakan UIImageJPEGRepresentationalih-alih UIImagePNGRepresentationuntuk mengurangi ukuran file gambar Anda. Anda bisa membuat ekstensi UIImage sebagai berikut:

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 / perbarui:

Xcode 10 Swift 4.2.0

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

Pemakaian:

if let imageData = image.jpeg(.lowest) {
    print(imageData.count)
}
Leo Dabus
sumber
1
Penggunaan tipe UIImage yang tidak dideklarasikan. Ini adalah kesalahan yang saya dapatkan
Umit Kaya
1
Saya mengembalikan gambar dengan ukuran 22-25MB, sekarang sebagian kecil dari itu. Terima kasih banyak! Perpanjangan yang bagus!
Octavio Antonio Cedeño
3
Tidak ada perbedaan selain sintaks. Tentu saja Anda dapat menulis kode secara manual UIImageJPEGRepresentation(yourImage, 1.0)daripada hanya mengetik .jpdan membiarkan xcode melengkapi metode tersebut secara otomatis untuk Anda. sama untuk penghitungan kompresi .whatever.
Leo Dabus
1
@Umitk Anda harus mengimpor UIKit
Alan
1
'jpegData (compressionQuality :)' telah diubah namanya menjadi 'UIImageJPEGRepresentation ( : :)'
5uper_0leh
53

Jika Anda ingin membatasi ukuran gambar menjadi beberapa nilai konkret, Anda dapat melakukan hal berikut:

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
    } 
}
hbk
sumber
Ini adalah solusi yang lebih komprehensif.
Idrees Ashraf
swift 3.1if let data = bits.representation(using: .jpeg, properties: [.compressionFactor:compressingValue])
Jacksonsox
16
Ini sangat mahal karena Anda melakukan tugas yang begitu berat dalam beberapa saat dan setiap saat !! tidak ada kondisi yang membatasi ..
Amber K
Komprehensif tetapi sangat sulit memori. Ini merusak perangkat lama dengan masalah memori. Pasti ada cara yang lebih murah untuk melakukan ini.
Tofu Warrior
1
Ini adalah ide yang sangat buruk, ini penuh dengan kasus tepi yang mengerikan. 1) Ini dimulai dengan nilai kompresi 1.0 yang berarti hampir tidak ada kompresi. Jika dimensi gambar kecil, maka gambar akan menjadi lebih banyak KB daripada yang mereka butuhkan. 2) Jika gambarnya besar, ini akan menjadi lambat karena mungkin dikompres ulang berkali-kali untuk mendapatkan di bawah ukuran target. 3) Jika gambar sangat besar, maka mungkin akan dikompres ke titik di mana gambar terlihat sampah. Dalam kasus tersebut, akan lebih baik jika tidak menyimpan gambar dan memberi tahu pengguna bahwa gambar terlalu besar.
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!)!
}
Tuan Javed Multani
sumber
9

Jus Fixing untuk Xcode 7, diuji pada 09/21/2015 dan berfungsi dengan baik:

Buat saja ekstensi UIImagesebagai berikut:

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

Kemudian Anda bisa menggunakannya seperti ini:

let imageData = imagePassed.lowestQualityJPEGNSData
Thiago Arreguy
sumber
Inti terima kasih untuk menjawab pemilik Tn. Thiago & editor Tn. Kuldeep
Abimanyu Rathore
5

Swift 4 Binary Approach untuk mengompres gambar

Saya yakin sudah cukup terlambat untuk menjawab pertanyaan ini tetapi inilah solusi saya untuk pertanyaan yang dioptimalkan. Saya menggunakan pencarian Biner untuk menemukan nilai optimal. Jadi, misalnya, katakanlah dengan pendekatan pengurangan normal untuk mencapai 62% akan membutuhkan 38 percobaan kompresi, pendekatan * Pencarian biner ** akan mencapai solusi yang dibutuhkan dalam max log (100) = sekitar 7 percobaan.

Namun, juga ingin memberi tahu Anda bahwa UIImageJPEGRepresentationfungsi tersebut tidak berperilaku linier terutama jika jumlahnya mendekati 1. Berikut adalah screen grab di mana kita dapat melihat bahwa gambar berhenti mengompresi setelah nilai float> 0,995. Perilakunya tidak dapat diprediksi jadi lebih baik memiliki buffer delta yang akan menangani kasus seperti itu.

masukkan deskripsi gambar di sini

Ini kodenya

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)!
    }
}
rahulg
sumber
Mengapa Anda menggunakan perulangan While dan menambahkan variabel secara manual? Gunakan saja for i in 0...13.
Peter Schorn
3

Itu sangat sederhana dengan UIImageekstensi

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

menggunakan

// max 300kb
image.resizeByByte(maxByte: 300000) { (resizedData) in
    print("image size: \(resizedData.count)")
}
Yusuf
sumber
4
sangat lambat dan sinkron
Iman kazemayni
1
mungkin saja gambar yang diubah ukurannya tidak pernah kurang dari 300kB dan Anda tidak memiliki fallback untuk kasus ini
slxl
setidaknya (compressQuality> = 0) dapat ditambahkan ke while loop sebagai kondisi ekstra &&
slxl
2

Pembaruan Swift 4.2 . Saya membuat ekstensi ini untuk mengurangi ukuran gambar UII.
Di sini Anda memiliki dua metode, yang pertama mengambil persentase dan yang kedua mengurangi gambar menjadi 1MB.
Tentu saja Anda dapat mengubah metode kedua menjadi 1KB atau ukuran yang Anda inginkan.

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
    }
}
sazz
sumber
2

Di Swift 5 sebagai jawaban @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)! }

}

Dan Anda bisa menelepon seperti ini:

let imageData = imagePassed.lowestQualityJPEGNSData
Eric
sumber
2

Di 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!
    }
Sai kumar Reddy
sumber
2

Penggunaan func jpegData(compressionQuality: CGFloat) -> Data?berfungsi dengan baik jika Anda tidak perlu mengompres ke ukuran tertentu. Namun, untuk gambar tertentu, saya merasa berguna untuk dapat mengompres di bawah ukuran file tertentu. Dalam hal itu,jpegData tidak dapat diandalkan, dan pengompresan berulang-ulang pada gambar menghasilkan stagnan pada ukuran file (dan bisa sangat mahal). Sebagai gantinya, saya lebih suka mengurangi ukuran UIImage itu sendiri, lalu mengonversi ke jpegData dan memeriksa untuk melihat apakah ukuran yang diperkecil berada di bawah nilai yang saya pilih (dalam margin kesalahan yang saya tetapkan). Saya menyesuaikan pengali langkah kompresi berdasarkan rasio ukuran file saat ini dengan ukuran file yang diinginkan untuk mempercepat iterasi pertama yang paling mahal.

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

Dan penggunaan:

let data = image.compress(to: 300)

resizedEkstensi UIImage berasal dari: Bagaimana cara mengubah ukuran UIImage untuk mengurangi ukuran gambar unggahan

Besi John Bonney
sumber
1

Cepat 3

@ leo-dabus jawaban direvisi untuk cepat 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)  }
}
fozoglu.dll
sumber
1

Di Swift 4 saya membuat ekstensi ini yang akan menerima ukuran yang diharapkan dan mencoba mencapainya.

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


}

Menggunakan

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

            })
        } catch {
                 print("Error")
        }
D Saggin
sumber