Comment compresser ou réduire la taille d'une image avant de la télécharger vers Parse as PFFile? (Rapide)


87

J'essayais de télécharger un fichier image sur Parse après avoir pris une photo directement sur le téléphone. Mais cela lève une exception:

Arrêt de l'application en raison d'une exception non interceptée 'NSInvalidArgumentException', raison: 'PFFile ne peut pas être plus grand que 10485760 octets'

Voici mon code:

Dans le contrôleur de première vue:

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

Dans le contrôleur de vue qui télécharge l'image:

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

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

Mais j'ai encore besoin de télécharger cette photo sur Parse. Existe-t-il un moyen de réduire la taille ou la résolution de l'image?


J'ai essayé la dernière proposition de gbk et fount à la fin que si j'appelle let newData = UIImageJPEGRepresentation (UIImage (data: data), 1) newData.count n'est pas égal à data.count et est vraiment plus grand avec un facteur de plus de 2. Ce qui est pour moi vraiment surprenant! Quoi qu'il en soit, merci pour le code!
NicoD

Réponses:


185

Oui, vous pouvez utiliser à la UIImageJPEGRepresentationplace de UIImagePNGRepresentationpour réduire la taille de votre fichier image. Vous pouvez simplement créer une extension UIImage comme suit:

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

modifier / mettre à jour:

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

Usage:

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

1
Utilisation du type non déclaré UIImage. C'est l'erreur que j'obtiens
Umit Kaya

1
Je retournais des images avec des tailles de 22-25 Mo, maintenant une fraction de cela. Merci beaucoup! Grande extension!
Octavio Antonio Cedeño

3
Il n'y a aucune différence autre que la syntaxe. Bien sûr, vous pouvez écrire manuellement le code UIImageJPEGRepresentation(yourImage, 1.0)au lieu de simplement taper .jpet laisser xcode compléter automatiquement la méthode pour vous. la même chose pour l'énumération de compression .whatever.
Leo Dabus

1
@Umitk vous devriez importer UIKit
Alan

1
'jpegData (compressionQuality :)' a été renommé en 'UIImageJPEGRepresentation ( : :)'
5uper_0leh

52

Si vous souhaitez limiter la taille de l'image à une valeur concrète, vous pouvez procéder comme suit:

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

C'est une solution plus complète.
Idrees Ashraf

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

14
C'est très cher, vous faites une tâche si lourde sur une boucle while et à chaque fois !! aucune condition limitative ..
Amber K

Complet mais très dur sur la mémoire. Cela plante les anciens appareils avec des problèmes de mémoire. Il doit y avoir un moyen moins coûteux de le faire.
Tofu Warrior

1
C'est une très mauvaise idée, elle regorge de cas de bord terribles. 1) Cela commence par compressingValue de 1.0, ce qui signifie pratiquement aucune compression. Si les dimensions de l'image sont petites, les images finiront par représenter beaucoup plus de Ko qu'elles n'en ont besoin. 2) Si les images sont grandes, cela sera lent car il peut se recompresser plusieurs fois pour passer sous la taille cible. 3) Si les images sont très volumineuses, elles peuvent se compresser au point où l'image semble mauvaise. Dans ces cas, il serait préférable de simplement ne pas enregistrer l'image et de dire à l'utilisateur qu'elle est trop grande.
Orion Edwards le

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

Correction de jus pour Xcode 7, testé le 21/09/2015 et fonctionne bien:

Créez simplement une extension UIImagecomme suit:

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

Ensuite, vous pouvez l'utiliser comme ceci:

let imageData = imagePassed.lowestQualityJPEGNSData

Merci au propriétaire de la réponse, M. Thiago et à l'éditeur, M. kuldeep
Abhimanyu Rathore

5

Approche binaire Swift 4 pour compresser l'image

Je pense qu'il est assez tard pour répondre à cette question mais voici ma solution à la question qui est optimisée J'utilise la recherche binaire pour trouver la valeur optimale. Ainsi, par exemple, disons que par une approche de soustraction normale pour atteindre 62%, il faudrait 38 tentatives de compression, l'approche de * recherche binaire ** atteindrait la solution requise en log max (100) = environ 7 tentatives.

Cependant, je tiens également à vous informer que la UIImageJPEGRepresentationfonction ne se comporte pas de manière linéaire surtout lorsque le nombre atteint près de 1. Voici la capture d'écran où nous pouvons voir que l'image cesse de se compresser après que la valeur flottante est> 0,995. Le comportement est assez imprévisible, il est donc préférable d'avoir un tampon delta qui gère de tels cas.

entrez la description de l'image ici

Voici le code pour cela

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

Pourquoi utilisez-vous une boucle While et incrémentez-vous manuellement une variable? Utilisez juste for i in 0...13.
Peter Schorn

2

C'est très simple avec l' UIImageextension

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

utiliser

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

4
très lent et synchronisé
iman kazemayni

1
il se peut que votre image redimensionnée ne soit jamais inférieure à 300 Ko et que vous n'ayez aucune solution de rechange pour ce cas
slxl

au moins (compressQuality> = 0) pourrait être ajouté à la boucle while en tant que condition && supplémentaire
slxl

2

Mise à jour Swift 4.2 . J'ai créé cette extension pour réduire la taille d'UIImage.
Ici, vous avez deux méthodes, la première prend un pourcentage et la seconde réduit l'image à 1 Mo.
Bien sûr, vous pouvez changer la deuxième méthode pour devenir 1 Ko ou n'importe quelle taille que vous voulez.

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

Dans Swift 5, répondez @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)! }

}

Et vous pouvez appeler comme ceci:

let imageData = imagePassed.lowestQualityJPEGNSData

2

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

2

L'utilisation func jpegData(compressionQuality: CGFloat) -> Data?fonctionne bien si vous n'avez pas besoin de compresser à une taille spécifique. Cependant, pour certaines images, je trouve utile de pouvoir compresser en dessous d'une certaine taille de fichier. Dans ce cas, jpegDatan'est pas fiable et la compression itérative d'une image entraîne un plafonnement de la taille du fichier (et peut être très coûteuse). Au lieu de cela, je préfère réduire la taille de l'UIImage elle-même, puis la convertir en jpegData et vérifier si la taille réduite est inférieure à la valeur que j'ai choisie (dans une marge d'erreur que j'ai définie). J'ajuste le multiplicateur de pas de compression en fonction du rapport entre la taille de fichier actuelle et la taille de fichier souhaitée pour accélérer les premières itérations qui sont les plus coûteuses.

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

Et l'utilisation:

let data = image.compress(to: 300)

L' resizedextension UIImage provient de: Comment redimensionner l'UIImage pour réduire la taille de l'image de téléchargement


1

Swift 3

@ réponse leo-dabus révisée pour 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)  }
}

1

Dans Swift 4, j'ai créé cette extension qui recevra la taille attendue pour essayer de l'atteindre.

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


}

Utiliser

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

            })
        } catch {
                 print("Error")
        }
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.