【问题标题】:How to compress of reduce the size of an image before uploading to Parse as PFFile? (Swift)如何在上传到 Parse as PFFile 之前压缩或减小图像的大小? (迅速)
【发布时间】:2015-06-25 22:11:27
【问题描述】:

直接在手机上拍照后,我试图将图像文件上传到 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。有什么办法可以减小图片的大小或分辨率?

【问题讨论】:

  • 我尝试了 gbk 的最后一个命题,最后发现如果我调用 let newData = UIImageJPEGRepresentation(UIImage(data: data), 1) newData.count 不等于 data.count 并且是真的更大,系数超过 2。这对我来说真的很令人惊讶!无论如何,谢谢你的代码!

标签: ios swift parse-platform uiimage


【解决方案1】:

是的,您可以使用UIImageJPEGRepresentation 而不是UIImagePNGRepresentation 来减小图像文件的大小。你可以创建一个扩展 UIImage 如下:

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

【讨论】:

  • 使用未声明类型的 UIImage。这是我得到的错误
  • 我返回的图像大小为 22-25MB,现在只是其中的一小部分。太感谢了!很棒的扩展!
  • 除了语法没有区别。当然,您可以手动编写代码UIImageJPEGRepresentation(yourImage, 1.0) 而不是只输入.jp 并让xcode 为您自动完成该方法。压缩枚举 .whatever 也是如此。
  • @Umitk 你应该导入 UIKit
  • 'jpegData(compressionQuality:)' 已重命名为 'UIImageJPEGRepresentation(::)'
【解决方案2】:

如果您想将图像的大小限制为某个具体值,您可以执行以下操作:

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

【讨论】:

  • 这是更全面的解决方案。
  • swift 3.1 if let data = bits.representation(using: .jpeg, properties: [.compressionFactor:compressingValue])
  • 这非常昂贵,您每次都在 while 循环中执行如此繁重的任务!没有限制条件..
  • 全面但真的很难记忆。这会使旧设备因内存问题而崩溃。必须有更便宜的方法来做到这一点。
  • 这是一个非常糟糕的主意,它充满了可怕的边缘情况。 1) 它从 compressingValue 1.0 开始,这意味着几乎没有任何压缩。如果图像尺寸很小,那么图像最终会比它们需要的多得多。 2)如果图像很大,它会很慢,因为它可能会重新压缩多次以低于目标大小。 3)如果图像非常大,那么它可能会压缩到图像看起来很垃圾的程度。在这些情况下,最好不要保存图像并告诉用户它太大了。
【解决方案3】:
  //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!)!
}

【讨论】:

    【解决方案4】:

    Xcode 7 的 Jus Fixing,于 2015 年 9 月 21 日测试并且工作正常:

    只需创建一个扩展 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
    

    【讨论】:

    • 核心感谢答案所有者Thiago先生和编辑kuldeep先生
    【解决方案5】:

    如果您不需要压缩到特定大小,则使用func jpegData(compressionQuality: CGFloat) -&gt; Data? 效果很好。但是,对于某些图像,我发现能够压缩到特定文件大小以下很有用。在这种情况下,jpegData 是不可靠的,并且图像的迭代压缩会导致文件大小停滞(并且可能非常昂贵)。相反,我更喜欢减小 UIImage 本身的大小,然后转换为 jpegData 并检查减小的大小是否低于我选择的值(在我设置的误差范围内)。我根据当前文件大小与所需文件大小的比率调整压缩步骤乘数,以加快最昂贵的第一次迭代。

    斯威夫特 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 扩展来自: How do I resize the UIImage to reduce upload image size

    【讨论】:

      【解决方案6】:

      Swift 4 Binary Approach 压缩图像

      我相信现在回答这个问题已经很晚了,但这是我对优化问题的解决方案,我正在使用 二分搜索 来找到最佳值。因此,例如,假设通过正常减法方法达到 62% 需要 38 次压缩尝试,*二分搜索** 方法将在最大 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)!
          }
      }
      

      【讨论】:

      • 为什么要使用 While 循环并手动递增变量?只需使用for i in 0...13
      【解决方案7】:

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

      SWIFT 5

      extension UIImage {
          
          func resizeByByte(maxByte: Int, completion: @escaping (Data) -> Void) {
              var compressQuality: CGFloat = 1
              var imageData = Data()
              var imageByte = self.jpegData(compressionQuality: 1)?.count
              
              while imageByte! > maxByte {
                  imageData = self.jpegData(compressionQuality: compressQuality)!
                  imageByte = self.jpegData(compressionQuality: compressQuality)?.count
                  compressQuality -= 0.1
              }
              
              if maxByte > imageByte! {
                  completion(imageData)
              } else {
                  completion(self.jpegData(compressionQuality: 1)!)
              }
          }
      }
      

      使用

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

      【讨论】:

      • 非常慢且同步
      • 可能会发生您调整大小的图像永远不会小于 300kB 并且您在这种情况下没有任何回退
      • 至少 (compressQuality >= 0) 可能会作为额外的 && 条件添加到 while 循环中
      【解决方案8】:

      Swift 4.2 更新。我创建了这个扩展来减少 UIImage 的大小。
      这里你有两种方法,第一种取百分比,第二种将图像缩小到 1MB。
      当然你可以把第二种方法改成 1KB 或者你想要的任何大小。

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

      【讨论】:

        【解决方案9】:

        在 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
        

        【讨论】:

          【解决方案10】:

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

          【讨论】:

            【解决方案11】:

            斯威夫特 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)  }
            }
            

            【讨论】:

              【解决方案12】:

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

              【讨论】:

                【解决方案13】:

                我发现最简单的方法是

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

                【讨论】:

                  最近更新 更多