Як стиснути зменшений розмір зображення перед завантаженням до Синтаксичний аналіз як PFFile? (Стрімкий)


87

Я намагався завантажити файл зображення в 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 (дані: дані), 1) newData.count не дорівнює data.count і насправді більший з фактором більше, ніж 2. Що для мене справді дивно! Як би там не було, дякую за код!
NicoD 03.03.17

Відповіді:


186

Так, ви можете використовувати 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-25 Мб, тепер це лише частка. Дуже дякую! Чудове продовження!
Октавіо Антоніо Седеньо

3
Різниці крім синтаксису немає. Звичайно, ви можете вручну написати код, UIImageJPEGRepresentation(yourImage, 1.0)а не просто ввести .jpі дозволити xcode автозавершити метод для вас. те саме для переліку стиснення .whatever.
Лео Дабус

1
@Umitk вам слід імпортувати UIKit
Алан,

1
'jpegData (compressionQuality :)' було перейменовано на 'UIImageJPEGRepresentation ( : :)'
5uper_0leh

52

Якщо ви хочете обмежити розмір зображення до певного конкретного значення, ви можете зробити наступне:

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

стрімкий 3.1if let data = bits.representation(using: .jpeg, properties: [.compressionFactor:compressingValue])
Jacksonsox

14
Це дуже дорого, ви виконуєте таке важке завдання на певний час і кожен раз !! без обмежувальних умов ..
Amber K

Всебічний, але дуже важкий для пам’яті. Це призводить до збою старих пристроїв із проблемами пам’яті. Це повинно бути дешевшим способом.
Воїн Тофу

1
Це дійсно погана ідея, вона повна страшних випадків краю. 1) Починається із стискання значення 1,0, що майже не означає стиснення. Якщо розміри зображення невеликі, то зображення в кінцевому підсумку набагато більше КБ, ніж їм потрібно. 2) Якщо зображення великі, це буде повільно, оскільки може багато разів перекомпресуватись, щоб отримати нижчий розмір. 3) Якщо зображення дуже великі, вони можуть стискатися до такої міри, що зображення виглядає сміттям. У таких випадках було б краще просто не зберегти зображення та повідомити користувача, що він занадто великий.
Оріон Едвардс

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

Основна подяка власнику відповіді містеру Тіаго та редактору містеру Кулдіпу
Абхіманю

5

Свіфт 4 двійковий підхід для стиснення зображення

Я вважаю, що пізно відповісти на це питання, але ось моє рішення оптимізованого питання. Я використовую двійковий пошук для пошуку оптимального значення. Так, наприклад, скажімо, за допомогою звичайного підходу віднімання для досягнення 62% потрібно 38 спроб стиснення, підхід * Двійковий пошук ** досягне необхідного рішення в max 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.
Пітер Шорн,

2

Це дуже просто з 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
може трапитися так, що розмір вашого зображення ніколи не буде меншим за 300 кБ, і у вас не буде жодної резервної
копії

принаймні (compressQuality> = 0) можна додати до циклу while як додаткову умову &&
slxl

2

Оновлення Swift 4.2 . Я створив це розширення, щоб зменшити розмір UIImage.
Тут у вас є два методи: перший бере відсоток, а другий зменшує зображення до 1 МБ.
Звичайно, ви можете змінити другий спосіб на 1 КБ або будь-який потрібний розмір.

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

У Свіфті

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)

resizedРозширення UIImage походить із: Як змінити розмір 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")
        }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.