Làm thế nào để nén giảm kích thước của hình ảnh trước khi tải lên Phân tích cú pháp dưới dạng PFFile? (Nhanh)


87

Tôi đang cố tải tệp hình ảnh lên Phân tích cú pháp sau khi chụp ảnh trực tiếp trên điện thoại. Nhưng nó ném ra một ngoại lệ:

Chấm dứt ứng dụng do ngoại lệ chưa được đề xuất 'NSInvalidArgumentException', lý do: 'PFFile không được lớn hơn 10485760 byte'

Đây là mã của tôi:

Trong bộ điều khiển góc nhìn thứ nhất:

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

Trong bộ điều khiển chế độ xem tải lên hình ảnh:

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

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

Nhưng tôi vẫn cần tải bức ảnh đó lên Parse. Có cách nào để giảm kích thước hoặc độ phân giải của hình ảnh?


Tôi đã thử mệnh đề cuối cùng của gbk và wat ở cuối rằng nếu tôi gọi let newData = UIImageJPEGRepresentation (UIImage (data: data), 1) newData.count không bằng data.count và thực sự lớn hơn với kho dữ liệu nhiều hơn 2. Điều đó đối với tôi thực sự đáng ngạc nhiên! Nhưng dù sao, cảm ơn vì mã!
NicoD

Câu trả lời:


185

Có, bạn có thể sử dụng UIImageJPEGRepresentationthay vì UIImagePNGRepresentationđể giảm kích thước tệp hình ảnh của mình. Bạn chỉ có thể tạo một UIImage tiện ích mở rộng như sau:

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

chỉnh sửa / cập nhật:

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

Sử dụng:

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

1
Sử dụng UIImage loại không khai báo. Đây là lỗi tôi nhận được
Umit Kaya

1
Tôi đã trả lại hình ảnh có kích thước 22-25MB, bây giờ là một phần nhỏ của nó. Cảm ơn bạn rất nhiều! Phần mở rộng tuyệt vời!
Octavio Antonio Cedeño

3
Không có sự khác biệt nào ngoài cú pháp. Tất nhiên, bạn có thể viết mã theo cách thủ công UIImageJPEGRepresentation(yourImage, 1.0)thay vì chỉ nhập .jpvà để xcode tự động điền phương thức cho bạn. tương tự đối với liệt kê nén .whatever.
Leo Dabus

1
@Umitk bạn nên nhập UIKit
Alan

1
'jpegData (compressionQuality :)' đã được đổi tên thành 'UIImageJPEGRepresentation ( : :)'
5uper_0leh

52

Nếu bạn muốn giới hạn kích thước hình ảnh ở một số giá trị cụ thể, bạn có thể làm như sau:

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

Đây là giải pháp toàn diện hơn.
Idrees Ashraf

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

14
Điều này rất tốn kém khi bạn đang làm một nhiệm vụ nặng nề như vậy trong một vòng lặp thời gian và mọi lúc !! không có điều kiện hạn chế ..
Amber K

Toàn diện nhưng thực sự khó về bộ nhớ. Điều này làm hỏng các thiết bị cũ hơn có vấn đề về bộ nhớ. Phải có một cách rẻ hơn để làm điều này.
Tofu Warrior

1
Đây là một ý tưởng thực sự tồi, nó chứa đầy những trường hợp cạnh khủng khiếp. 1) Nó bắt đầu với giá trị nén 1.0, nghĩa là hầu như không có bất kỳ quá trình nén nào. Nếu kích thước hình ảnh nhỏ, thì hình ảnh sẽ có kích thước lớn hơn nhiều KB hơn mức cần thiết. 2) Nếu hình ảnh lớn, nó sẽ chậm vì nó có thể nén lại nhiều lần để có kích thước mục tiêu. 3) Nếu hình ảnh rất lớn, thì nó có thể nén xuống đến mức hình ảnh trông như rác. Trong những trường hợp đó, sẽ tốt hơn nếu không lưu hình ảnh và nói với người dùng rằng nó quá lớn.
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

Jus Fixing cho Xcode 7, được thử nghiệm vào ngày 21/09/2015 và hoạt động tốt:

Chỉ cần tạo một tiện ích mở rộng UIImagenhư sau:

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

Sau đó, bạn có thể sử dụng nó như thế này:

let imageData = imagePassed.lowestQualityJPEGNSData

Cảm ơn cốt lõi đối với chủ sở hữu câu trả lời, ông Thiago và biên tập viên ông kuldeep
Abhimanyu Rathore.

5

Phương pháp tiếp cận nhị phân Swift 4 để nén hình ảnh

Tôi tin rằng đã khá muộn để trả lời câu hỏi này nhưng đây là giải pháp của tôi cho câu hỏi được tối ưu hóa Tôi đang sử dụng tìm kiếm nhị phân để tìm giá trị tối ưu. Vì vậy, ví dụ, giả sử theo phương pháp trừ thông thường để đạt được 62% sẽ yêu cầu 38 lần thử nén, cách tiếp cận * Tìm kiếm nhị phân ** sẽ đạt được giải pháp cần thiết trong log max (100) = khoảng 7 lần thử.

Tuy nhiên, cũng xin thông báo với bạn rằng UIImageJPEGRepresentationhàm không hoạt động tuyến tính đặc biệt là khi con số đạt gần 1. Đây là màn hình lấy ra nơi chúng ta có thể thấy rằng hình ảnh ngừng nén sau khi giá trị float> 0.995. Hành vi này là khá khó đoán vì vậy tốt hơn là có một bộ đệm delta sẽ xử lý các trường hợp như vậy.

nhập mô tả hình ảnh ở đây

Đây là mã cho nó

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

Tại sao bạn lại sử dụng vòng lặp While và tăng một biến theo cách thủ công? Chỉ cần sử dụng for i in 0...13.
Peter Schorn

2

Điều đó rất đơn giản với UIImagetiện ích mở rộng

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

sử dụng

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

4
rất chậm và đồng bộ
iman kazemayni

1
nó có thể xảy ra rằng hình ảnh thay đổi kích cỡ của bạn sẽ không bao giờ không được thấp hơn 300KB và bạn không có bất kỳ dự phòng cho trường hợp này
slxl

ít nhất (compressQuality> = 0) có thể được thêm vào trong khi vòng lặp như thêm && điều kiện
slxl

2

Bản cập nhật Swift 4.2 . Tôi đã tạo tiện ích mở rộng này để giảm kích thước UIImage.
Ở đây bạn có hai phương pháp, phương pháp thứ nhất lấy tỷ lệ phần trăm và phương pháp thứ hai giảm hình ảnh xuống 1MB.
Tất nhiên bạn có thể thay đổi phương pháp thứ hai để trở thành 1KB hoặc bất kỳ kích thước nào bạn muốn.

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

Trong Swift 5 dưới dạng @Thiago Arreguy, câu trả lời:

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

}

Và bạn có thể gọi như thế này:

let imageData = imagePassed.lowestQualityJPEGNSData

2

Trong 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

Việc sử dụng func jpegData(compressionQuality: CGFloat) -> Data?hoạt động tốt nếu bạn không cần nén đến một kích thước cụ thể. Tuy nhiên, đối với một số hình ảnh nhất định, tôi thấy rất hữu ích khi có thể nén xuống dưới một kích thước tệp nhất định. Trong trường hợp đó, jpegDatalà không đáng tin cậy và việc nén lặp đi lặp lại một hình ảnh dẫn đến việc giảm kích thước tệp (và có thể thực sự tốn kém). Thay vào đó, tôi muốn giảm kích thước của chính UIImage, sau đó chuyển đổi thành jpegData và kiểm tra xem kích thước đã giảm có nằm dưới giá trị tôi đã chọn hay không (trong phạm vi sai số mà tôi đã đặt). Tôi điều chỉnh hệ số nhân bước nén dựa trên tỷ lệ của kích thước tệp hiện tại với kích thước tệp mong muốn để tăng tốc độ lặp lại đầu tiên vốn đắt nhất.

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

Và cách sử dụng:

let data = image.compress(to: 300)

resizedPhần mở rộng UIImage đến từ: Làm cách nào để thay đổi kích thước của UIImage để giảm kích thước hình ảnh tải lên


1

Swift 3

@ leo-dabus câu trả lời đã được sửa đổi cho nhanh chóng 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

Trong Swift 4, tôi đã tạo phần mở rộng này, phần mở rộng này sẽ nhận được kích thước mong đợi nếu bạn cố gắng đạt được nó.

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


}

Để sử dụng

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

            })
        } catch {
                 print("Error")
        }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.