Как изменить размер UIImage, чтобы уменьшить размер загружаемого изображения

Я искал в 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 МБ? Спасибо!

21 ответ

Решение

Xcode 8.2.1 • Swift 3.0.2

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 resized(toWidth width: CGFloat) -> UIImage? {
        let canvasSize = CGSize(width: width, height: CGFloat(ceil(width/size.width * size.height)))
        UIGraphicsBeginImageContextWithOptions(canvasSize, false, scale)
        defer { UIGraphicsEndImageContext() }
        draw(in: CGRect(origin: .zero, size: canvasSize))
        return UIGraphicsGetImageFromCurrentImageContext()
    }
}

Использование:

let myPicture = UIImage(data: try! Data(contentsOf: URL(string:"https://stackru.com/images/ef9dfaef8c3bec7e67a69584eb37fe5a3e115132.jpg")!))!

let myThumb1 = myPicture.resized(withPercentage: 0.1)
let myThumb2 = myPicture.resized(toWidth: 72.0)

Это путь, которым я следовал, чтобы изменить размер изображения.

 -(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 = UIImageJPEGRepresentation(img!, CGFloat(compressionQuality))
    UIGraphicsEndImageContext()
    return UIImage(data: imageData!) ?? UIImage()
}

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

Поэтому я использовал ответ @Leo Dabus и переписал его в вспомогательную структуру, которая преобразует изображение в фоновую очередь:

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

В случае, если кто-то ищет изменение размера изображения менее 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()

так же, как Лео Ответ, но мало правок для 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
    }
}

Swift4.2

  let imagedata = yourImage.jpegData(compressionQuality: 0.1)!

Вот ответ пользователя 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)!

}

Я думаю, что суть вопроса здесь в том, как надежно сжать данные UIImage до определенного размера перед загрузкой на сервер, а не просто сжать сам UIImage.

С помощью func jpegData(compressionQuality: CGFloat) -> Data?хорошо работает, если вам не нужно сжимать до определенного размера. Однако в некоторых случаях я считаю полезным иметь возможность сжимать файл ниже определенного указанного размера. В этом случае,jpegDataненадежен, и итеративное сжатие изображения таким образом приводит к снижению размера файла (и может быть очень дорогостоящим). Вместо этого я предпочитаю уменьшить размер самого UIImage, как в ответе Лео, затем преобразовать в jpegData и итеративно проверить, находится ли уменьшенный размер ниже выбранного мной значения (в пределах, которые я установил). Я регулирую множитель шага сжатия на основе отношения текущего размера файла к желаемому, чтобы ускорить первые итерации, которые являются самыми дорогими (поскольку размер файла является самым большим в этот момент).

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

iOS 15+ Свифт 5

Часть решений здесь не отвечает на вопрос, потому что они не создают изображение с меньшим размером файла для его загрузки в серверную часть. Очень важно не загружать большие файлы изображений в серверную часть, когда это на самом деле не нужно. Это займет гораздо больше места, будет дороже храниться и займет больше времени для загрузки, что приведет к тому, что пользовательский интерфейс будет ждать контента.

Многие ответы используют либо

      UIGraphicsImageRenderer(size: canvas).image {
    _ in draw(in: CGRect(origin: .zero, size: canvas))
}

Или старше

      UIGraphicsGetImageFromCurrentImageContext()

Проблема с этими решениями заключается в том, что они генерируют UIImage меньшего размера , но не изменяют базовый CGImage , поэтому, когда вы пытаетесь отправить изображение как DATA , вы заметите, что загружаете UIImage , но данные из базового CGImage , размер которого не изменяется и имеет большой размер файла.

Другие решения принудительно сжимаютjpedDataдо наименьших доступных, которые приводят к очень большому сжатию и потере качества.

Чтобы на самом деле изменить размер изображения со всеми лежащими в его основе вещами и отправить его как действительно маленький jpeg-метод лучшего качества, используйте методpreparingThumbnail(of:)и установить.jpegData(compressionQuality:)до 8 или 9.

      extension UIImage {
    func thumbnail(width: CGFloat) -> UIImage? {
        guard size.width > width else { return self }
        let imageSize = CGSize(
            width: width,
            height: CGFloat(ceil(width/size.width * size.height))
        )
        return preparingThumbnail(of: imageSize)
    }
}

Вот документация , готовящая Thumbnail(of:)

Если вы загружаете изображение в NSData формат, используйте это:

NSData *imageData = UIImageJPEGRepresentation(yourImage, floatValue);

yourImage твой UIImage,floatvalue это значение сжатия (от 0,0 до 1,0)

Выше, чтобы преобразовать изображение в JPEG,

За PNGиспользовать: UIImagePNGRepresentation

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

В Swift 5.5 с использованием async/await и image. pngData() , а не .jpegData(compressionQuality: 1.0), чтобы получить правильное представление данных изображения:

      
import UIKit

public struct ImageCompressor {
    
    private static func getPercentageToDecreaseTo(forDataCount dataCount: Int) -> CGFloat {
           switch dataCount {
           case 0..<3000000: return 0.05
           case 3000000..<10000000: return 0.1
           default: return 0.2
           }
       }
    static public func compressAsync(image: UIImage, maxByte: Int) async -> UIImage? {
        guard let currentImageSize = image.pngData()?.count else { return nil }
        var iterationImage: UIImage? = image
        var iterationImageSize = currentImageSize
        var iterationCompression: CGFloat = 1.0
        
        while iterationImageSize > maxByte && iterationCompression > 0.01 {
            let percentageDecrease = getPercentageToDecreaseTo(forDataCount: iterationImageSize)
            
            let canvasSize = CGSize(width: image.size.width * iterationCompression,
                                    height: image.size.height * iterationCompression)
            /*
            UIGraphicsBeginImageContextWithOptions(canvasSize, false, image.scale)
            defer { UIGraphicsEndImageContext() }
            image.draw(in: CGRect(origin: .zero, size: canvasSize))
            iterationImage = UIGraphicsGetImageFromCurrentImageContext()
            */
            iterationImage = await image.byPreparingThumbnail(ofSize: canvasSize)
            guard let newImageSize = iterationImage?.pngData()?.count else {
                return nil
            }
            iterationImageSize = newImageSize
            iterationCompression -= percentageDecrease
        }
        
        
        
        return iterationImage
    }
    
}

Я думаю, что самый простой способ - это использовать swift для сжатия изображения в сжатые данные. Это код в swift 4.2.

let imageData = yourImageTobeCompressed.jpegData(compressionQuality: 0.5)

и вы можете отправить эту imageData для загрузки на сервер.

Используя это, вы можете контролировать размер, который вы хотите:

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, но только попытаться раз. Если произойдет сбой в течение этого времени, он вернет ноль.

Основано на ответе Tung Fam. Чтобы изменить размер до определенного размера файла. Как 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)
}
}

То же самое в 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];

Это то, что я сделал в Swift 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)

Я столкнулся с этим вопросом, исследуя сжатие и экспорт изображений в Swift, и использовал его в качестве отправной точки, чтобы лучше понять проблему и разработать более эффективную технику.

Процесс UIGraphicsBeginImageContext(), UIGraphicsGetImageFromCurrentImageContext(), UIGraphicsEndImageContext() - это более старый метод, который был заменен UIGraphicsImageRenderer, используемым Iron John Bonney и Leo Dabus. Их примеры были написаны как расширения на UIImage, тогда как я решил написать независимую функцию. Необходимые различия в подходах можно определить путем сравнения (посмотрите на вызов UIGraphicsImageRenderer и рядом с ним), и их можно легко перенести обратно в расширение UIImage.

Я подумал, что есть потенциал для улучшения используемых здесь алгоритмов сжатия, поэтому я применил подход, который начал с настройки изображения, чтобы оно имело заданное общее количество пикселей, а затем сжало его, настроив сжатие jpeg для достижения указанного конечного размера файла. . Задача указания общего количества пикселей заключалась в том, чтобы избежать проблем с соотношением сторон изображения. Хотя я не провел исчерпывающего исследования, я подозреваю, что масштабирование изображения до указанного общего количества пикселей приведет к тому, что конечный размер файла изображения jpeg будет в общем диапазоне, а затем сжатие jpeg может быть использовано для обеспечения ограничения размера файла. достигается с приемлемым качеством изображения, если исходное количество пикселей не слишком велико.

При использовании UIGraphicsImageRenderer CGRect указывается в логических пикселях на главном устройстве Apple, что отличается от фактических пикселей в выходном jpeg. Посмотрите соотношение пикселей устройства, чтобы понять это. Чтобы получить соотношение пикселей устройства, я попытался извлечь его из среды, но эти методы вызвали сбой игровой площадки, поэтому я использовал менее эффективный метод, который работал.

Если вы вставите этот код в игровую площадку Xcode и поместите соответствующий файл .jpg в папку «Ресурсы», выходной файл будет помещен в выходную папку «Игровая площадка» (используйте «Быстрый просмотр» в интерактивном просмотре, чтобы найти это место).

      import UIKit

func compressUIImage(_ image: UIImage?, numPixels: Int, fileSizeLimitKB: Double, exportImage: Bool) -> Data {
    var returnData: Data
    if let origWidth = image?.size.width,
       let origHeight = image?.size.height {
        print("Original image size =", origWidth, "*", origHeight, "pixels")

        let imgMult = min(sqrt(CGFloat(numPixels)/(origWidth * origHeight)), 1) // This multiplier scales the image to have the desired number of pixels
        print("imageMultiplier =", imgMult)

        let cgRect = CGRect(origin: .zero, size: CGSize(width: origWidth * imgMult, height: origHeight * imgMult)) // This is in *logical* pixels
        let renderer = UIGraphicsImageRenderer(size: cgRect.size)
        
        let img = renderer.image { ctx in
            image?.draw(in: cgRect)
        }
        
        // Now get the device pixel ratio if needed...
        var img_scale: CGFloat = 1
        if exportImage {
            img_scale = img.scale
        }
        print("Image scaling factor =", img_scale)

        // ...and use to ensure *output* image has desired number of pixels
        let cgRect_scaled = CGRect(origin: .zero, size: CGSize(width: origWidth * imgMult/img_scale, height: origHeight * imgMult/img_scale)) // This is in *logical* pixels
        print("New image size (in logical pixels) =", cgRect_scaled.width, "*", cgRect_scaled.height, "pixels") // Due to device pixel ratios, can have fractional pixel dimensions

        let renderer_scaled = UIGraphicsImageRenderer(size: cgRect_scaled.size)

        let img_scaled = renderer_scaled.image { ctx in
            image?.draw(in: cgRect_scaled)
        }

        var compQual = CGFloat(1.0)

        returnData = img_scaled.jpegData(compressionQuality: 1.0)!
        var imageSizeKB = Double(returnData.count) / 1000.0
        
        print("compressionQuality =", compQual, "=> imageSizeKB =", imageSizeKB, "KB")
        while imageSizeKB > fileSizeLimitKB {
            compQual *= 0.9
            returnData = img_scaled.jpegData(compressionQuality: compQual)!
            imageSizeKB = Double(returnData.count) / 1000.0
            print("compressionQuality =", compQual, "=> imageSizeKB =", imageSizeKB, "KB")
        }
    } else {
        returnData = Data()
    }
    return returnData
}

let image_orig = UIImage(named: "input.jpg")

let image_comp_data = compressUIImage(image_orig, numPixels: Int(4e6), fileSizeLimitKB: 1300, exportImage: true)

func getDocumentsDirectory() -> URL {
    let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    return paths[0]
}

let filename = getDocumentsDirectory().appendingPathComponent("output.jpg")

try? image_comp_data.write(to: filename)

Источники включают Джордан Морган и Hacking with Swift.

Когда я пытаюсь использовать принятый ответ, чтобы изменить размер изображения для использования в моем проекте, оно получается очень пиксельным и размытым. Я закончил с этим фрагментом кода, чтобы изменить размер изображения без добавления пикселов или размытия:

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
    }

Если кому-то нужно, вот асинхронная версия, измененная из ответа Али Пакмана :

      import UIKit

extension UIImage {
    func compress(to maxByte: Int) async -> UIImage? {
        let compressTask = Task(priority: .userInitiated) { () -> UIImage? in
            guard let currentImageSize = jpegData(compressionQuality: 1.0)?.count else {
                return nil
            }

            var iterationImage: UIImage? = self
            var iterationImageSize = currentImageSize
            var iterationCompression: CGFloat = 1.0
            
            while iterationImageSize > maxByte && iterationCompression > 0.01 {
                let percentageDecrease = getPercentageToDecreaseTo(forDataCount: iterationImageSize)
                let canvasSize = CGSize(width: size.width * iterationCompression, height: size.height * iterationCompression)
                
                UIGraphicsBeginImageContextWithOptions(canvasSize, false, scale)
                defer { UIGraphicsEndImageContext() }
                draw(in: CGRect(origin: .zero, size: canvasSize))
                iterationImage = UIGraphicsGetImageFromCurrentImageContext()
                
                guard let newImageSize = iterationImage?.jpegData(compressionQuality: 1.0)?.count else {
                    return nil
                }
                iterationImageSize = newImageSize
                iterationCompression -= percentageDecrease
            }
            
            return iterationImage
        }
        return await compressTask.value
    }
    
    private func getPercentageToDecreaseTo(forDataCount dataCount: Int) -> CGFloat {
        switch dataCount {
        case 0..<3000000: return 0.05
        case 3000000..<10000000: return 0.1
        default: return 0.2
        }
    }
}

Измените размер UIImage, используя.resizeToMaximumBytes

extension UIImage {
func resized(toValue value: CGFloat) -> UIImage {
    if size.width > size.height {
        return self.resize(toWidth: value)!
    } else {
        return self.resize(toHeight: value)!
    }
}
Другие вопросы по тегам