Parse를 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에 업로드해야합니다. 이미지의 크기 나 해상도를 줄이는 방법이 있습니까?


나는 let newData = UIImageJPEGRepresentation (UIImage (data : data), 1) newData.count가 data.count와 같지 않고 실제로 더 큰 인자로 더 크다는 것을 마지막에 gbk의 마지막 제안과 fount를 시도했습니다. 2. 정말 놀랍습니다! 어쨌든 코드 감사합니다!
NicoD

답변:


185

예, 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 사용. 이것은 내가 오류입니다
미트 가야

1
22-25MB 크기의 이미지를 반환하고 있었는데, 지금은 그 일부입니다. 정말 고맙습니다! 훌륭한 확장!
Octavio Antonio Cedeño

3
구문 외에 다른 점은 없습니다. 물론 UIImageJPEGRepresentation(yourImage, 1.0)입력하는 대신 수동으로 코드 를 작성하고 .jpxcode가 자동으로 메소드를 완성하도록 할 수 있습니다. 압축 열거에 대해서도 동일합니다 .whatever.
Leo Dabus

1
당신은 UIKit 가져와야합니다 @Umitk
앨런

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

이것은보다 포괄적 인 솔루션입니다.
이드리스 라프

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

14
while 루프에서 그리고 매번 그렇게 무거운 작업을 수행하는 것은 매우 비쌉니다! 제한 조건 없음 ..
Amber K

포괄적이지만 메모리가 정말 어렵습니다. 이로 인해 메모리 문제로 오래된 장치가 충돌합니다. 이를 수행하는 더 저렴한 방법이 있어야합니다.
두부 전사

1
이것은 정말 나쁜 생각이며 끔찍한 엣지 케이스로 가득 차 있습니다. 1) compressingValue 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

Xcode 7에 대한 Jus Fixing은 2015 년 9 월 21 일에 테스트되었으며 정상적으로 작동합니다.

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

대답 소유자 씨 티아고 및 편집기 씨 kuldeep에 코어 덕분에
Abhimanyu 토레

5

이미지 압축을 위한 Swift 4 Binary Approach

이 질문에 대답하는 것이 꽤 늦었다 고 생각하지만 여기에 최적화 된 질문에 대한 내 솔루션이 있습니다. 최적의 값을 찾기 위해 이진 검색 을 사용 하고 있습니다. 예를 들어 일반적인 빼기 방식으로 62 %에 도달하려면 38 번의 압축 시도가 필요하고 * 바이너리 검색 ** 방식은 최대 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.
Peter Schorn

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
당신의 크기를 조정 한 이미지가 적은 300KB 이하 여야하지 못할 일이 발생할 수 있으며이 경우에 대한 대체가 없습니다
slxl

적어도 (compressQuality> = 0)은 추가 && 조건으로 while 루프에 추가 될 수 있습니다
slxl

2

Swift 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

@Thiago Arreguy 대답으로 Swift 5에서 :

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.