본문 바로가기
IOS - Swift

[iOS/Swift] UIkit image Resizing ( 크기 / 품질)

by 게게겍 2023. 8. 6.

이미지 리사이징을 해야할 일이 생겨서 리사이징을 하는 방법을 찾던중 다양한 방법이 있는것을 알게 되었습니다.

하나는 공식적으로 메서드인 UIGraphicsImageRenderer 이고 다른 하나는 jpegData(compressionQuality:) 메서드를 사용하여 UIImage의 JPEG 압축하는 방식입니다.

UIGraphicsImageRenderer란?

Core Graphics 지원 이미지를 만들기 위한 그래픽 렌더러입니다.

참고 wwdc 2018 Memory Deep Dive

 

UIGraphicsImageRenderer는 iOS 10.0 이후에 사용할 수 있는 API로, 그래픽스 및 이미지 렌더링에 최적화된 컨텍스트를 제공합니다. 이를 사용하면 Core Graphics의 복잡한 API를 사용하지 않고도 스위프트 코드로 간단하게 이미지를 그릴 수 있습니다.

 

도형 그리기 예시

import UIKit

func drawShapes() -> UIImage {
    let size = CGSize(width: 300, height: 300)
    
    let renderer = UIGraphicsImageRenderer(size: size)
    
    let image = renderer.image { ctx in
        // Background color
        UIColor.lightGray.setFill()
        ctx.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height))
        
        // Draw a red circle
        UIColor.red.setFill()
        let circleRect = CGRect(x: 50, y: 50, width: 100, height: 100)
        ctx.cgContext.fillEllipse(in: circleRect)
        
        // Draw a blue rectangle with a border
        UIColor.blue.setFill()
        UIColor.black.setStroke()
        let rect = CGRect(x: 50, y: 150, width: 100, height: 50)
        ctx.cgContext.fill(rect)
        ctx.cgContext.stroke(rect, width: 2)
        
        // Draw a line segment
        ctx.cgContext.move(to: CGPoint(x: 200, y: 50))
        ctx.cgContext.addLine(to: CGPoint(x: 250, y: 150))
        UIColor.green.setStroke()
        ctx.cgContext.setLineWidth(5)
        ctx.cgContext.strokePath()
        
        // Draw text
        let textAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.boldSystemFont(ofSize: 20),
            .foregroundColor: UIColor.white
        ]
        let string = "Hello!"
        string.draw(at: CGPoint(x: 200, y: 200), withAttributes: textAttributes)
    }
    
    return image
}
  1. UIGraphicsImageRenderer 생성:
    • size는 그릴 이미지의 크기를 정의합니다.
    • UIGraphicsImageRenderer(size: size)를 사용하여 renderer 객체를 생성합니다.
  2. 도형 및 텍스트 그리기:
    • renderer.image { ctx in ... }는 그래픽스 컨텍스트를 제공하며, 이 컨텍스트를 사용하여 도형을 그립니다.
    • 원, 사각형, 선 등을 그립니다
  3. 색상 설정:
    • UIColor.setFill()UIColor.setStroke() 메서드를 사용하여 채우기 및 테두리 색상을 설정합니다.
  4. 도형 그리기:
    • ctx.cgContext.fillEllipse(in:)는 원을 그립니다.
    • ctx.cgContext.fill(rect:)는 사각형을 채웁니다.
    • ctx.cgContext.stroke(rect:, width:)는 사각형에 테두리를 그립니다.
    • ctx.cgContext.move(to:)ctx.cgContext.addLine(to:)를 사용하여 선을 그립니다.
  5. 텍스트 그리기:
    • String.draw(at:withAttributes:)를 사용하여 텍스트를 그립니다.

즉, 이런식으로 이미지를 새로그려서 물리적인 크기를 줄이는데 목적을 두고 있습니다

→ 2000x2000의 이미지를 500x500으로 바꾸는 것

하지만 제가 하던 프로젝트에서는

[Body]: None
[Response]:
  [Status Code]: 413
  [Headers]:
 
    Content-Length: 192
    Content-Type: text/html
    ...
  [Body]:
    <html>
    <head><title>413 Request Entity Too Large</title></head>

이런식으로 이미지의 크기가 커서 서버에 post하기 곤란한 것이였습니다.

또한, Post 하려는 부분이

이런식으로 개인의 프로필 부분이였기 때문에 이미지의 물리적인 크기가 줄어든다면 프로필에 표시되는 이미지의 크기가 어색하게 줄어드는 경우가 생기기 때문에 파일 크기 (데이터 크기)를 줄이는데 중점을 두어야 햇습니다.

따라서 JPEG 포맷의 압축률을 조절하는 방식으로 이미지의 크기를 줄엿습니다.

func compressImage(_ image: UIImage, toSizeInMB maxSizeInMB: Double) -> Data? {
        let maxSizeInBytes = maxSizeInMB * 1024 * 1024
        var compressionQuality: CGFloat = 1.0 // 1MB 될때까지 계속 while문 반복하기
        var imageData: Data?
        while compressionQuality > 0 {
            imageData = image.jpegData(compressionQuality: compressionQuality)
            if let imageData = imageData, Double(imageData.count) <= maxSizeInBytes {
                print("Compressed image size: \\(Double(imageData.count) / 1024 / 1024) MB")
                break
            }
            compressionQuality -= 0.1
        }
        return imageData
    }

extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        if let image = info[.editedImage] as? UIImage {
            ProfileImageView.image = image

            if let compressedImageData = compressImage(image, toSizeInMB: 1.0) {
                print("Compressed image data size: \\(Double(compressedImageData.count) / 1024 / 1024) MB")

               
            picker.dismiss(animated: true, completion: nil)
        }
    }
}

이 방법은 원본 이미지의 픽셀 크기를 그대로 유지하면서도 이미지 데이터의 크기(바이트 단위)를 조절할수 있는 방식입니다.

이를 위해 UIImage의 jpegData(compressionQuality:) 메서드를 사용해 이미지를 JPEG 형식으로 압축합니다. compressionQuality 매개변수를 조절하면 이미지의 압축률을 조절할 수 있으며, 이런 방식으로 이미지 품질을 직접적으로 떨어뜨립니다.

 

제가 선택한 이유는 프로필 이미지의 모양이 깨지는것보다 프로필 이미지를 직접적으로 사용할 일이 없기 때문에 품질을 저하시키더라도 이러한 방식으로 파일 크기를 줄이는 방식이 좀더 나은 방식임을 알게 되어 적용하게 되었습니다.