Як змінити розмір UIImage, щоб зменшити розмір завантаженого зображення


78

Я шукав у Google і стикався лише з бібліотеками, які або зменшують висоту / ширину, або деякі способи редагування зовнішнього вигляду UIImage за допомогою CoreImage. Але я не бачив і не знайшов жодної бібліотеки, публікації, яка пояснює, як зменшити розмір зображення, тому, коли воно завантажується, це не повний розмір зображення.

поки що я маю це:

        if image != nil {
        //let data = NSData(data: UIImagePNGRepresentation(image))
        let data = UIImagePNGRepresentation(image)
        body.appendString("--\(boundary)\r\n")
        body.appendString("Content-Disposition: form-data; name=\"image\"; filename=\"randomName\"\r\n")
        body.appendString("Content-Type: image/png\r\n\r\n")
        body.appendData(data)
        body.appendString("\r\n")
    }

і надсилає 12 Мб фотографій. Як я можу зменшити це до 1 Мб? Дякую!

Відповіді:


191

Xcode 9 • Swift 4 або новішої версії

редагування / оновлення: для iOS10 + Ми можемо використовувати UIGraphicsImageRenderer . Для старішого синтаксису Swift перевірте історію редагування.

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 resized(toWidth width: CGFloat, isOpaque: Bool = true) -> UIImage? {
        let canvas = CGSize(width: width, height: CGFloat(ceil(width/size.width * size.height)))
        let format = imageRendererFormat
        format.opaque = isOpaque
        return UIGraphicsImageRenderer(size: canvas, format: format).image {
            _ in draw(in: CGRect(origin: .zero, size: canvas))
        }
    }
}

Використання:

let image = UIImage(data: try! Data(contentsOf: URL(string:"http://i.stack.imgur.com/Xs4RX.jpg")!))!

let thumb1 = image.resized(withPercentage: 0.1)
let thumb2 = image.resized(toWidth: 72.0)

2
Чудова штука, Лео. Дякую.
MolonLabe

2
слід додати швидкий синтаксис 2 нижче. хтось скопіював / вставив ваш код і набрав 9 голосів "за". незаслужено, якщо ви запитаєте мене ...
Девід

7
LeoDabus, я думаю, що це не правильна відповідь, але дуже гарне рішення для інших проблем. Питання тут полягає в тому, як ми можемо зменшити 12 МБ до 1 МБ не на основі відсотків та розміру зображення (ширина чи висота), а на основі розміру файлу. @EBDOKUM може мати такий результат через те, що зменшення розміру зображення (ширина та висота) не гарантує зменшення розміру файлу. Зменшується лише розмір, але розмір файлу може збільшуватися або зменшуватися залежно від використовуваного зображення.
nycdanie

3
@ChrisW. Не кажучи вже про фотошоп чи веб-движок. Не соромтеся розміщувати свою відповідь на питання, де показано, як стиснути PNG у iOS (Swift).
Лео Дабус

3
Слід додати, guard size.width > width else { return self }щоб менші зображення не змінювались, щоб бути більшими.
том

70

Це шлях, яким я пішов, щоб змінити розмір зображення.

 -(UIImage *)resizeImage:(UIImage *)image
{
   float actualHeight = image.size.height;
   float actualWidth = image.size.width;
   float maxHeight = 300.0;
   float maxWidth = 400.0;
   float imgRatio = actualWidth/actualHeight;
   float maxRatio = maxWidth/maxHeight;
   float compressionQuality = 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;
    }
   }

   CGRect rect = CGRectMake(0.0, 0.0, actualWidth, actualHeight);
   UIGraphicsBeginImageContext(rect.size);
   [image drawInRect:rect];
   UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
   NSData *imageData = UIImageJPEGRepresentation(img, compressionQuality);
   UIGraphicsEndImageContext();

   return [UIImage imageWithData:imageData];

}

За допомогою цього методу моє зображення розміром 6,5 МБ зменшено до 104 КБ.

Код Swift 4:

func resize(_ image: UIImage) -> UIImage {
    var actualHeight = Float(image.size.height)
    var actualWidth = 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 = CGRect(x: 0.0, y: 0.0, width: CGFloat(actualWidth), height: CGFloat(actualHeight))
    UIGraphicsBeginImageContext(rect.size)
    image.draw(in: rect)
    let img = UIGraphicsGetImageFromCurrentImageContext()
    let imageData = img?.jpegData(compressionQuality: CGFloat(compressionQuality)) 
    UIGraphicsEndImageContext()
    return UIImage(data: imageData!) ?? UIImage()
}

як щодо дозволу? Я хотів би зробити ширину максимум 1024, але висота буде залежати від зображення, тому я можу зберегти пропорції.
Джей,

Тут я також стискаю зображення. Якщо ви хочете з однаковим дозволом, прокоментуйте цей рядок NSData * imageData = UIImageJPEGRepresentation (img, compressionQuality); і ми повинні вибрати maxHeight і maxWidth таким чином, щоб співвідношення сторін було однаковим.
user4261201

1
Як надати мінімальну ширину та висоту. Отже, це зображення не стиснуте нижче конкретного розміру
user2185354

SWIFT 4.2 змінює цей рядок: нехай imageData = UIImageJPEGRepresentation (img !, CGFloat (compressionQuality)) До цього: нехай imageData = img? .JpegData (compressionQuality: CGFloat (compressionQuality))
ttorbik

30

Якщо хтось шукає зменшення розміру зображення до розміру менше 1 Мб за допомогою Swift 3 і 4 .

Просто скопіюйте та вставте це розширення:

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 = UIImagePNGRepresentation(self) else { return nil }

    var resizingImage = self
    var imageSizeKB = Double(imageData.count) / 1000.0 // ! Or devide for 1024 if you need KB but not kB

    while imageSizeKB > 1000 { // ! Or use 1024 if you need KB but not kB
        guard let resizedImage = resizingImage.resized(withPercentage: 0.9),
            let imageData = UIImagePNGRepresentation(resizedImage)
            else { return nil }

        resizingImage = resizedImage
        imageSizeKB = Double(imageData.count) / 1000.0 // ! Or devide for 1024 if you need KB but not kB
    }

    return resizingImage
}
}

І використовувати:

let resizedImage = originalImage.resizedTo1MB()

Редагувати: Зверніть увагу, що він блокує інтерфейс користувача , тому перейдіть до фонового потоку, якщо вважаєте, що це правильний спосіб для вашої справи.


В whileроках циклу guard, то UIImagePNGRepresentationвиклик повинен використовувати resizedImage, а НЕ resizingImage. Змушує це робити додатковий цикл ...
Mike M

12

те саме, що Leo Answer, але невеликі правки для SWIFT 2.0

 extension UIImage {
    func resizeWithPercentage(percentage: CGFloat) -> UIImage? {
        let imageView = UIImageView(frame: CGRect(origin: .zero, size: CGSize(width: size.width * percentage, height: size.height * percentage)))
        imageView.contentMode = .ScaleAspectFit
        imageView.image = self
        UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, scale)
        guard let context = UIGraphicsGetCurrentContext() else { return nil }
        imageView.layer.renderInContext(context)
        guard let result = UIGraphicsGetImageFromCurrentImageContext() else { return nil }
        UIGraphicsEndImageContext()
        return result
    }

    func resizeWithWidth(width: CGFloat) -> UIImage? {
        let imageView = UIImageView(frame: CGRect(origin: .zero, size: CGSize(width: width, height: CGFloat(ceil(width/size.width * size.height)))))
        imageView.contentMode = .ScaleAspectFit
        imageView.image = self
        UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, scale)
        guard let context = UIGraphicsGetCurrentContext() else { return nil }
        imageView.layer.renderInContext(context)
        guard let result = UIGraphicsGetImageFromCurrentImageContext() else { return nil }
        UIGraphicsEndImageContext()
        return result
    }
}

2
коментар про те, яка різниця була б непоганою
Девід Сік

Логіка була такою ж, але лео-відповідь стосувалася швидкої версії 3.0, і те, що я зробив, просто написало її у швидкій версії 2.0.
Jagdeep,

5

Ось відповідь user4261201, але швидко, що я зараз використовую:

func compressImage (_ image: UIImage) -> UIImage {

    let actualHeight:CGFloat = image.size.height
    let actualWidth:CGFloat = image.size.width
    let imgRatio:CGFloat = actualWidth/actualHeight
    let maxWidth:CGFloat = 1024.0
    let resizedHeight:CGFloat = maxWidth/imgRatio
    let compressionQuality:CGFloat = 0.5

    let rect:CGRect = CGRect(x: 0, y: 0, width: maxWidth, height: resizedHeight)
    UIGraphicsBeginImageContext(rect.size)
    image.draw(in: rect)
    let img: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
    let imageData:Data = UIImageJPEGRepresentation(img, compressionQuality)!
    UIGraphicsEndImageContext()

    return UIImage(data: imageData)!

}

1
Перетворювати UIImage на дані і назад на UIImage безглуздо. До речі, ви погіршуєте якість зображення, стискаючи його.
Лео Дабус

1
@LeoDabus дякую за це розуміння, я вибрав вашу відповідь як правильну!
Джей,


4

Мене тут не влаштовували рішення, які генерують зображення на основі заданого розміру КБ, оскільки більшість із них використовуються .jpegData(compressionQuality: x). Цей метод не працюватиме з великими зображеннями, оскільки навіть при якості стиснення, встановленому на 0,0, велике зображення залишатиметься великим, наприклад, 10 МБ, вироблені в портретному режимі новішого iPhone, все ще будуть вище 1 МБ, а для якості якості стиснення встановлено значення 0,0.

Тому я використав відповідь @Leo Dabus і переписав її у Helper Struct, яка перетворює зображення у фоновій que:

import UIKit

struct ImageResizer {
    static func resize(image: UIImage, maxByte: Int, completion: @escaping (UIImage?) -> ()) {
        DispatchQueue.global(qos: .userInitiated).async {
            guard let currentImageSize = image.jpegData(compressionQuality: 1.0)?.count else { return completion(nil) }
            
            var imageSize = currentImageSize
            var percentage: CGFloat = 1.0
            var generatedImage: UIImage? = image
            let percantageDecrease: CGFloat = imageSize < 10000000 ? 0.1 : 0.3
            
            while imageSize > maxByte && percentage > 0.01 {
                let canvas = CGSize(width: image.size.width * percentage,
                                    height: image.size.height * percentage)
                let format = image.imageRendererFormat
                format.opaque = true
                generatedImage = UIGraphicsImageRenderer(size: canvas, format: format).image {
                    _ in image.draw(in: CGRect(origin: .zero, size: canvas))
                }
                guard let generatedImageSize = generatedImage?.jpegData(compressionQuality: 1.0)?.count else { return completion(nil) }
                imageSize = generatedImageSize
                percentage -= percantageDecrease
            }
            
            completion(generatedImage)
        }
    }
}

Використання:

        ImageResizer.resize(image: image, maxByte: 800000) { img in
            guard let resizedImage = img else { return }
            // Use resizedImage
        }
    }

3
Бажаю, щоб я міг підтримати це 100 разів. Справжня відповідь, люди.
wxcoder

2

Якщо ви завантажуєте зображення у NSDataформаті, використовуйте це:

NSData *imageData = UIImageJPEGRepresentation(yourImage, floatValue);

yourImageє вашим UIImage. floatvalue- значення стиснення (від 0,0 до 1,0)

Вище наведено перетворення зображення в JPEG.

Для PNGвикористання:UIImagePNGRepresentation

Примітка: Наведений вище код знаходиться в Objective-C. Будь ласка, перевірте, як визначити NSData в Swift.


якщо мене турбує роздільна здатність, як би я встановив максимальну ширину, але нехай висота визначається самим зображенням?
Джей,

2
UIImagePNGRepresentation не має можливості стиснення.
Алек.

1

За допомогою цього ви можете контролювати потрібний розмір:

func jpegImage(image: UIImage, maxSize: Int, minSize: Int, times: Int) -> Data? {
    var maxQuality: CGFloat = 1.0
    var minQuality: CGFloat = 0.0
    var bestData: Data?
    for _ in 1...times {
        let thisQuality = (maxQuality + minQuality) / 2
        guard let data = image.jpegData(compressionQuality: thisQuality) else { return nil }
        let thisSize = data.count
        if thisSize > maxSize {
            maxQuality = thisQuality
        } else {
            minQuality = thisQuality
            bestData = data
            if thisSize > minSize {
                return bestData
            }
        }
    }
    return bestData
}

Приклад виклику методу:

jpegImage(image: image, maxSize: 500000, minSize: 400000, times: 10)  

Він спробує отримати файл між максимальним та мінімальним розміром maxSize та minSize, але намагається лише разів раз. Якщо він не вдасться протягом цього часу, він поверне нуль.


1

Я думаю, що найпростіший спосіб забезпечує сам swift для стиснення зображення у стислі дані нижче - це код у swift 4.2

let imageData = yourImageTobeCompressed.jpegData(compressionQuality: 0.5)

і ви можете надіслати ці imageData для завантаження на сервер.


0

Це те, що я зробив у швидкому 3 для зміни розміру UIImage. Це зменшує розмір зображення до менш ніж 100 кб. Це працює пропорційно!

extension UIImage {
    class func scaleImageWithDivisor(img: UIImage, divisor: CGFloat) -> UIImage {
        let size = CGSize(width: img.size.width/divisor, height: img.size.height/divisor)
        UIGraphicsBeginImageContext(size)
        img.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
        let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return scaledImage!
    }
}

Використання:

let scaledImage = UIImage.scaleImageWithDivisor(img: capturedImage!, divisor: 3)

0

На основі відповіді Тунга Фама. Змінити розмір до певного розміру файлу. Як і 0,7 МБ, ви можете використовувати цей код.

extension UIImage {

func resize(withPercentage percentage: CGFloat) -> UIImage? {
    var newRect = CGRect(origin: .zero, size: CGSize(width: size.width*percentage, height: size.height*percentage))
    UIGraphicsBeginImageContextWithOptions(newRect.size, true, 1)
    self.draw(in: newRect)
    defer {UIGraphicsEndImageContext()}
    return UIGraphicsGetImageFromCurrentImageContext()
}

func resizeTo(MB: Double) -> UIImage? {
    guard let fileSize = self.pngData()?.count else {return nil}
    let fileSizeInMB = CGFloat(fileSize)/(1024.0*1024.0)//form bytes to MB
    let percentage = 1/fileSizeInMB
    return resize(withPercentage: percentage)
}
}

0

Те саме в Objective-C:

інтерфейс:

@interface UIImage (Resize)

- (UIImage *)resizedWithPercentage:(CGFloat)percentage;
- (UIImage *)resizeTo:(CGFloat)weight isPng:(BOOL)isPng jpegCompressionQuality:(CGFloat)compressionQuality;

@end

реалізація:

#import "UIImage+Resize.h"

@implementation UIImage (Resize)

- (UIImage *)resizedWithPercentage:(CGFloat)percentage {
    CGSize canvasSize = CGSizeMake(self.size.width * percentage, self.size.height * percentage);
    UIGraphicsBeginImageContextWithOptions(canvasSize, false, self.scale);
    [self drawInRect:CGRectMake(0, 0, canvasSize.width, canvasSize.height)];
    UIImage *sizedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return sizedImage;
}

- (UIImage *)resizeTo:(CGFloat)weight isPng:(BOOL)isPng jpegCompressionQuality:(CGFloat)compressionQuality {
    NSData *imageData = isPng ? UIImagePNGRepresentation(self) : UIImageJPEGRepresentation(self, compressionQuality);
    if (imageData && [imageData length] > 0) {
        UIImage *resizingImage = self;
        double imageSizeKB = [imageData length] / weight;

        while (imageSizeKB > weight) {
            UIImage *resizedImage = [resizingImage resizedWithPercentage:0.9];
            imageData = isPng ? UIImagePNGRepresentation(resizedImage) : UIImageJPEGRepresentation(resizedImage, compressionQuality);
            resizingImage = resizedImage;
            imageSizeKB = (double)(imageData.length / weight);
        }

        return resizingImage;
    }
    return nil;
}

Використання:

#import "UIImage+Resize.h"

UIImage *resizedImage = [self.picture resizeTo:2048 isPng:NO jpegCompressionQuality:1.0];

0

Коли я намагаюся використовувати прийняту відповідь, щоб змінити розмір зображення для використання в моєму проекті, він виходить дуже піксельним і розмитим. Я отримав цей фрагмент коду, щоб змінити розмір зображень без додавання пікселізації або розмиття:

func scale(withPercentage percentage: CGFloat)-> UIImage? {
        let cgSize = CGSize(width: size.width * percentage, height: size.height * percentage)

        let hasAlpha = true
        let scale: CGFloat = 0.0 // Use scale factor of main screen

        UIGraphicsBeginImageContextWithOptions(cgSize, !hasAlpha, scale)
        self.draw(in: CGRect(origin: CGPoint.zero, size: cgSize))

        let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
        return scaledImage
    }

0
extension UIImage {
func resized(toValue value: CGFloat) -> UIImage {
    if size.width > size.height {
        return self.resize(toWidth: value)!
    } else {
        return self.resize(toHeight: value)!
    }
}

0

Я думаю, що основне питання тут полягає в тому, як надійно зменшити дані UIImage до певного розміру перед завантаженням на сервер, а не просто зменшувати сам UIImage.

Використання 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 {
        guard kb > 10 else { return Data() } // Prevents user from compressing below a limit (10kb in this case).
        let bytes = kb * 1024
        var compression: CGFloat = 1.0
        let step: CGFloat = 0.05
        var holderImage = self
        var complete = false
        while(!complete) {
            guard let data = holderImage.jpegData(compressionQuality: 1.0) else { break }
            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: 1000)

Ви повертаєте порожні дані. Я припускаю, ви маєте на увазі return data?
Bawenang Rukmoko Pardian Putra

@BawenangRukmokoPardianPutra Це не викликається в цій функції, це просто задовольняє тип повернення функції. Дані повертаються в операторі if циклу while. Ймовірно, я мав би переформатувати відповідь, щоб кинути або використовувати необов’язкові засоби, але я пішов для простоти використання. Я також повинен додати заяву гвардії, щоб заборонити стиснення до нереально малих значень.
Айрон Джон Бонні

Додано оператор захисту для запобігання compress to 0регістру та обмеження стиснення до розумного значення (10 кб у цьому прикладі).
Iron John Bonney

Ага, так, погано. Я бачу це зараз.
Bawenang Rukmoko Pardian Putra

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.