วิธีการบีบอัดย่อขนาดของรูปภาพก่อนอัพโหลดไปยัง Parse เป็น PFFile? (อย่างรวดเร็ว)


88

ฉันพยายามอัปโหลดไฟล์ภาพไปยัง 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 และ fount ในตอนท้ายว่าถ้าฉันเรียกให้ newData = UIImageJPEGRepresentation (UIImage (data: data), 1) newData.count ไม่เท่ากับ data.count และมีขนาดใหญ่กว่าจริง ๆ โดยมีจำนวนมากกว่า 2. ซึ่งสำหรับฉันแล้วน่าแปลกใจจริงๆ! อย่างไรก็ตามขอบคุณสำหรับรหัส!
NicoD

คำตอบ:


188

ได้คุณสามารถใช้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)
}

1
การใช้ UIImage ประเภทที่ไม่ได้ประกาศ นี่คือข้อผิดพลาดที่ฉันได้รับ
Umit Kaya

1
ฉันกำลังส่งคืนรูปภาพที่มีขนาด 22-25MB ตอนนี้เป็นส่วนหนึ่ง ขอบคุณมาก! ต่อยอด!
Octavio Antonio Cedeño

3
ไม่มีความแตกต่างนอกเหนือจากไวยากรณ์ แน่นอนว่าคุณสามารถเขียนโค้ดด้วยตนเองUIImageJPEGRepresentation(yourImage, 1.0)แทนที่จะพิมพ์.jpและให้ xcode เติมข้อความอัตโนมัติให้กับคุณ .whateverเหมือนกันสำหรับการบีบอัดการแจงนับ
Leo Dabus

1
@Umitk คุณควรนำเข้า UIKit
Alan

1
'jpegData (compressionQuality :)' ถูกเปลี่ยนชื่อเป็น 'UIImageJPEGRepresentation ( : :)'
5uper_0leh

53

หากคุณต้องการ จำกัด ขนาดของภาพให้เป็นมูลค่าคอนกรีตคุณสามารถทำได้ดังนี้:

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

นี่คือโซลูชันที่ครอบคลุมมากขึ้น
Idrees Ashraf

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

16
แพงมากคุณทำงานหนักแบบนี้มาตลอดและทุกครั้ง !! ไม่มีเงื่อนไข จำกัด ..
Amber K

หน่วยความจำที่ครอบคลุม แต่ยากจริงๆ สิ่งนี้ขัดข้องอุปกรณ์รุ่นเก่าที่มีปัญหาหน่วยความจำ จะต้องมีวิธีที่ถูกกว่าในการทำเช่นนี้
Tofu Warrior

1
นี่เป็นความคิดที่แย่จริงๆมันเต็มไปด้วยกรณีขอบที่น่ากลัว 1) เริ่มต้นด้วยการบีบอัดค่า 1.0 ซึ่งหมายความว่าแทบจะไม่มีการบีบอัด หากขนาดของภาพมีขนาดเล็กภาพจะมี KB มากกว่าที่จำเป็น 2) หากภาพมีขนาดใหญ่ภาพจะช้าเนื่องจากอาจบีบอัดซ้ำหลายครั้งเพื่อให้ได้ขนาดที่ต่ำกว่าเป้าหมาย 3) หากภาพมีขนาดใหญ่มากภาพนั้นอาจบีบอัดลงไปจนถึงจุดที่ภาพดูเป็นขยะ ในกรณีดังกล่าวจะเป็นการดีกว่าหากไม่บันทึกรูปภาพและแจ้งให้ผู้ใช้ทราบว่ามีขนาดใหญ่เกินไป
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 สำหรับ Xcode 7 ทดสอบเมื่อ 21/09/2015 และทำงานได้ดี:

เพียงสร้างส่วนขยาย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 & บรรณาธิการคุณกุลดีพ
Abhimanyu Rathore

5

Swift 4 Binary Approachเพื่อบีบอัดภาพ

ฉันเชื่อว่ามันค่อนข้างช้าที่จะตอบคำถามนี้ แต่นี่คือคำตอบของฉันสำหรับคำถามที่ได้รับการปรับให้เหมาะสมฉันใช้การค้นหาแบบไบนารีเพื่อค้นหาค่าที่เหมาะสมที่สุด ตัวอย่างเช่นพูดโดยวิธีการลบปกติเพื่อให้ถึง 62% จะต้องใช้ความพยายามในการบีบอัด 38 ครั้งวิธีการ * ค้นหาแบบไบนารี ** จะเข้าถึงโซลูชันที่ต้องการในบันทึกสูงสุด (100) = ประมาณ 7 ครั้ง

อย่างไรก็ตามขอแจ้งให้คุณทราบด้วยว่าUIImageJPEGRepresentationฟังก์ชันดังกล่าวไม่ทำงานเป็นเชิงเส้นโดยเฉพาะเมื่อตัวเลขใกล้ถึง 1 นี่คือการจับหน้าจอที่เราจะเห็นว่าภาพหยุดบีบอัดหลังจากค่า float เท่ากับ> 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.
Peter Schorn

3

ง่ายมากกับ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)!)
    }
}

ใช้

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

4
ช้ามากและซิงค์
iman kazemayni

1
อาจเกิดขึ้นได้ว่าภาพที่ปรับขนาดของคุณจะต้องไม่น้อยกว่า 300kB และคุณไม่มีทางเลือกสำหรับกรณีนี้
slxl

อย่างน้อย (compressQuality> = 0) อาจถูกเพิ่มเข้าไปใน while loop เป็นเงื่อนไขพิเศษ &&
slxl

2

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

2

ใน 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

2

ใน 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

การใช้func jpegData(compressionQuality: CGFloat) -> 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ส่วนขยายมาจาก: ฉันจะปรับขนาด UIImage เพื่อลดขนาดภาพอัปโหลดได้อย่างไร


1

สวิฟต์ 3

คำตอบ @ leo-dabus แก้ไขเพื่อความรวดเร็ว 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

ใน 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")
        }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.