Уменьшение UIImage перед отправкой на сервер


#1

Задача после добавления фото оптимизировать его вес.

На данные момент используется расширение для изменения размера фото до минимально необходимого

    extension UIImage {

        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 compressData = image.jpegData(compressionQuality: ratio)
    image = UIImage(data: compressData!)!

Но результат не устраивает.
Либо слишком тяжелый файл, либо низкое качество.

Возможно существует некий “волшебный” способ уменьшить размер при сохранении качества.
Может библиотека, или еще что-то.
Вдруг кто-то знает?

Благодарю!


#2

это сколько? __________


#3

На мой взгляд, он не так чтоб и тяжелый 350 кб.
Но заказчик смотрит на приложение для Mac - ImageOptim
И хочет подобный результат на iOs


#4

Какой формат файла? Есть ли альфа-канал?

Можно перекидывать jpeg, там есть настройки качества изображения, но в этом случае, вся прозрачность улетает

Ага увидел! Вот с компрессией и стоит поиграться.


#5

jpeg
Без альфы
А какие еще настройки есть?


#6

Вот тут смотрите. Можно понизить качество без сильного ущерба для изображения


#7

Можете попробовать ему объяснить, что там свое кодирование графики, которое тянет на отдельный проект и соответственно увеличивает стоимость разработки. В вашем случае только базовая оптимизация, ну или изучать CoreImage


#8

Благодарю! Будем двигаться.


#9

Жму через КорГрафикс под размер 1024 пикселя:

static func compressImage(url: URL) -> Data? {
        let sourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
        
        guard let source = CGImageSourceCreateWithURL(url as CFURL, sourceOptions) else {return nil}
        
        let downsampleOptions = [
            kCGImageSourceCreateThumbnailFromImageAlways: true,
            kCGImageSourceCreateThumbnailWithTransform: true,
            kCGImageSourceThumbnailMaxPixelSize: 1_024,
        ] as CFDictionary

        guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions) else {return nil}

        let data = NSMutableData()
        
        guard let imageDestination = CGImageDestinationCreateWithData(data, UTType.jpeg.identifier as CFString, 1, nil) else {return nil}
        
        let isPNG: Bool = {
            guard let utType = cgImage.utType else { return false }
            return (utType as String) == UTType.png.identifier
        }()

        let destinationProperties = [
            kCGImageDestinationLossyCompressionQuality: isPNG ? 1.0 : 0.75
        ] as CFDictionary

        CGImageDestinationAddImage(imageDestination, cgImage, destinationProperties)
        CGImageDestinationFinalize(imageDestination)
        
        return data as Data
    }

потом джипегом ещё на коэф 0.8 - получаются приличного качества в размер до 150кб


#10

Будем внедрять. Благодарю!


#11

День добрый!
Если есть возможность — ответьте пожалуйста на пару вопросов.

1. Правильно ли я использую метод?

получаю Image из UIImagePickerController

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
    
    if let editedImage = info[UIImagePickerController.InfoKey.editedImage] as? UIImage {
        image = editedImage
    } else if let originalImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
        image = autofixImageOrientation(originalImage)
        
        if #available(iOS 14.0, *) {
            
            image = UIImage(data: compressImage(imageSource: image)!)!

// вычисление размера картинки
            let formatter = ByteCountFormatter()          
            formatter.allowedUnits = ByteCountFormatter.Units.useKB
            formatter.countStyle = ByteCountFormatter.CountStyle.file
            let formatted = formatter.string(fromByteCount: Int64(compressImage(imageSource: image)!.count))
            print(formatted, "  - size")
            
        } 
    }
}

В вашем методе поменял источник фото с

guard let source = CGImageSourceCreateWithURL(url as CFURL, sourceOptions) else {return nil}

на

let compressData = imageSource.jpegData(compressionQuality: 1)! as CFData
let imageSource = CGImageSourceCreateWithData(compressData, sourceOptions)

2. Заметил странную вещь:
Когда вычисляю размер фото вот тут let formatter = ByteCountFormatter() получается число примерно вдвое меньшее чем размер в свойствах файла jpeg скаченного с сервера Amazon где физически хранятся изображения. Почему так может происходить?


#12

Ну так вы смотрите размер уже пережатого изображения - поэтому и отличается от исходника! Вы же для этого и просили алгоритмы сжатия…

kCGImageSourceThumbnailMaxPixelSize: 1_024
вот в этой строке можно менять нужный вам размер преобразования


#14

Так я вроде и смотрю размер сжатого…
Основная задача сжать фото перед отправкой на сервер.
Я его выбираю из библиотеки на устройстве. Потом сжимаю. Смотрю первый раз замер
Потом отправляю на сервер. Потом его скачиваю с сервера и смотрю втрой раз размер.


#15

О, ну это уже другое. Перед отправкой жмёт нормально? Если да, то дальше уже вам виднее.


#16

Буду разбираться. _____