Jak skompresować lub zmniejszyć rozmiar obrazu przed przesłaniem do Parse as PFFile? (Szybki)

88

Próbowałem przesłać plik obrazu do Parse po zrobieniu zdjęcia bezpośrednio na telefonie. Ale rzuca wyjątek:

Zakończenie aplikacji z powodu nieprzechwyconego wyjątku „NSInvalidArgumentException”, przyczyna: „PFFile nie może być większa niż 10485760 bajtów”

Oto mój kod:

W pierwszym widoku kontroler:

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

W widoku kontrolera, który przesyła obraz:

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

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

Ale nadal muszę przesłać to zdjęcie do Parse. Czy istnieje sposób na zmniejszenie rozmiaru lub rozdzielczości obrazu?

elvislkm
źródło
Wypróbowałem ostatnią propozycję gbk i fount na końcu, że jeśli wywołam let newData = UIImageJPEGRepresentation (UIImage (data: data), 1) newData.count nie jest równe data.count i jest naprawdę większe z faktorem większym niż 2. Co jest dla mnie naprawdę zaskakujące! W każdym razie dzięki za kod!
NicoD

Odpowiedzi:

188

Tak, możesz użyć UIImageJPEGRepresentationzamiast, UIImagePNGRepresentationaby zmniejszyć rozmiar pliku obrazu. Możesz po prostu utworzyć rozszerzenie UIImage w następujący sposób:

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

edytuj / aktualizuj:

Xcode 10 Swift 4.2.0 Łatka

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

Stosowanie:

if let imageData = image.jpeg(.lowest) {
    print(imageData.count)
}
Leo Dabus
źródło
1
Korzystanie z niezadeklarowanego typu UIImage. To jest błąd, który otrzymuję
Umit Kaya
1
Zwracałem obrazy o rozmiarze 22-25 MB, teraz ułamek tego. Dziękuję bardzo! Świetne rozszerzenie!
Octavio Antonio Cedeño
3
Nie ma różnicy poza składnią. Oczywiście możesz ręcznie napisać kod UIImageJPEGRepresentation(yourImage, 1.0)zamiast po prostu pisać .jpi pozwolić xcode na autouzupełnianie metody za Ciebie. to samo dla wyliczenia kompresji .whatever.
Leo Dabus
1
@Umitk powinieneś zaimportować UIKit
Alan
1
Nazwa „jpegData (compressQuality :)” została zmieniona na „UIImageJPEGRepresentation ( : :)”
5uper_0leh
53

Jeśli chcesz ograniczyć rozmiar obrazu do określonej wartości, możesz wykonać następujące czynności:

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
źródło
To jest bardziej kompleksowe rozwiązanie.
Idrees Ashraf
swift 3.1if let data = bits.representation(using: .jpeg, properties: [.compressionFactor:compressingValue])
Jacksonsox
16
To bardzo kosztowne, wykonujesz tak ciężkie zadanie w pętli while i za każdym razem !! brak warunków ograniczających ..
Amber K
Wszechstronne, ale bardzo trudne w pamięci. Powoduje to awarię starszych urządzeń z problemami z pamięcią. Musi być na to tańszy sposób.
Tofu Warrior
1
To naprawdę zły pomysł, jest pełen okropnych skrajnych przypadków. 1) Rozpoczyna się kompresją Wartość 1,0, co oznacza prawie żadną kompresję. Jeśli wymiary obrazu są małe, obrazy będą miały o wiele więcej KB niż potrzebują. 2) Jeśli obrazy są duże, będzie działać wolno, ponieważ może wielokrotnie ponownie kompresować, aby uzyskać rozmiar poniżej docelowego. 3) Jeśli obrazy są bardzo duże, może się zmniejszyć do punktu, w którym obraz wygląda na śmieci. W takich przypadkach lepiej byłoby po prostu nie zapisać obrazu i powiedzieć użytkownikowi, że jest za duży.
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!)!
}
Panie Javed Multani
źródło
9

Jus Fixing for Xcode 7, testowane 21.09.2015 i działa poprawnie:

Po prostu utwórz rozszerzenie UIImagew następujący sposób:

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

Następnie możesz go użyć w ten sposób:

let imageData = imagePassed.lowestQualityJPEGNSData
Thiago Arreguy
źródło
Główne podziękowania dla właściciela odpowiedzi, pana Thiago i redaktora pana kuldeepa
Abhimanyu Rathore
5

Podejście binarne Swift 4 do kompresji obrazu

Uważam, że jest już dość późno, aby odpowiedzieć na to pytanie, ale oto moje rozwiązanie na pytanie, które jest zoptymalizowane. Używam wyszukiwania binarnego, aby znaleźć optymalną wartość. Na przykład, powiedzmy, że przy normalnym podejściu odejmowania osiągnięcie 62% wymagałoby 38 prób kompresji, podejście * Wyszukiwanie binarne ** zapewniłoby wymagane rozwiązanie w maksymalnym logu (100) = około 7 prób.

Jednak chciałbym również poinformować, że UIImageJPEGRepresentationfunkcja nie zachowuje się liniowo, zwłaszcza gdy liczba zbliża się do 1. Oto zrzut ekranu, na którym widać, że obraz przestaje się kompresować po tym, jak wartość float> 0,995. Zachowanie jest dość nieprzewidywalne, więc lepiej mieć bufor delta, który obsługiwałby takie przypadki.

wprowadź opis obrazu tutaj

Oto kod

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
źródło
Dlaczego używasz pętli While i ręcznie zwiększasz zmienną? Po prostu użyj for i in 0...13.
Peter Schorn
3

To bardzo proste z UIImagerozszerzeniem

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

używać

// max 300kb
image.resizeByByte(maxByte: 300000) { (resizedData) in
    print("image size: \(resizedData.count)")
}
Yusuf
źródło
4
bardzo wolno i synchronicznie
iman kazemayni
1
może się zdarzyć, że obraz o zmienionym rozmiarze nigdy nie będzie mniejszy niż
300 kB
co najmniej (kompresjaQuality> = 0) może zostać dodana do pętli while jako dodatkowy warunek &&
slxl
2

Aktualizacja Swift 4.2 . Stworzyłem to rozszerzenie, aby zmniejszyć rozmiar UIImage.
Tutaj masz dwie metody, pierwsza pobiera procent, a druga zmniejsza obraz do 1 MB.
Oczywiście możesz zmienić drugą metodę na 1KB lub dowolny rozmiar.

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
źródło
2

W Swift 5 jako @Thiago Arreguy odpowiedz:

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

}

Możesz zadzwonić w ten sposób:

let imageData = imagePassed.lowestQualityJPEGNSData
Eric
źródło
2

W 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
źródło
2

Używanie func jpegData(compressionQuality: CGFloat) -> Data?działa dobrze, jeśli nie musisz kompresować do określonego rozmiaru. Jednak w przypadku niektórych obrazów przydatna jest możliwość kompresji pliku poniżej określonego rozmiaru. W takim przypadku jpegDatajest zawodne, a iteracyjna kompresja obrazu skutkuje wyhamowaniem rozmiaru pliku (i może być naprawdę kosztowna). Zamiast tego wolę zmniejszyć rozmiar samego UIImage, a następnie przekonwertować go na jpegData i sprawdzić, czy zmniejszony rozmiar jest poniżej wybranej przeze mnie wartości (w granicach ustawionego przeze mnie marginesu błędu). Dostosowuję mnożnik kroku kompresji na podstawie stosunku rozmiaru bieżącego pliku do żądanego rozmiaru, aby przyspieszyć pierwsze iteracje, które są najdroższe.

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

I zastosowanie:

let data = image.compress(to: 300)

resizedRozszerzenie UIImage pochodzi z: Jak zmienić rozmiar UIImage, aby zmniejszyć rozmiar przesyłanego obrazu

Żelazny John Bonney
źródło
1

Szybki 3

Odpowiedź @ leo-dabus poprawiona pod kątem szybkiej 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
źródło
1

W Swift 4 stworzyłem to rozszerzenie, które otrzyma oczekiwany rozmiar i spróbuje do niego dotrzeć.

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


}

Używać

        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
źródło