이미지 처리 - Team-HGD/SniffMEET GitHub Wiki

이미지 처리의 다양한 방법들에 대해 알아보고자 정리했습니다.

Data 이용 방식

Data 객체를 이용해 이미지 데이터를 바이너리 형식으로 처리하는 방식으로

파일에서 읽어온 이미지 데이터나 네트워크를 통해 받은 이미지 데이터를 다룰 때 주로 사용합니다.

  • 간단하게 이미지 생성 가능
    • UIImage(data: )

예시 코드

let filePath = "path/image.jpg"
if let imageData = try? Data(contentsOf: URL(fileURLWithPath: filePath)) {
    let image = UIImage(data: imageData)
    imageView.image = image
}

💡 Data(contentsOf: ) 이용해 이미지 불러오기 지양해야 한다

위의 예시 코드에서 처럼 Data(contentsOf: ) 이용해 URL을 넣어 이미지를 불러오는 방법이 있다.

하지만 이 방식을 이용하는 것은 지양해야 한다고 한다. 그 이유는 아래와 같다.

URL 로딩은 UI 반응성에 영향을 줄 수 있어 메인 스레드에서 동작하면 안된다고 한다.

예를들어, 큰 이미지를 불러오거나 네트워크 환경이 좋지 않은 경우, URL 로딩에 시간이 걸릴 수 있는데 이 작업이 메인 스레드에서 동작하면 UI 조작이 어려워지기 때문이다.

따라서 큰 이미지 작업 등(혹은 대부분의 성능 향상?을 위해서)에서는 URLSession같은 비동기 통신 API 사용을 권장한다.

💡 이미지를 Data 형태로 UserDefault에 저장하는 것은 부적합하다

UserDefault는 기본 시스템 설정 및 사용자 환경 설정 등의 작은 데이터들을 저장하는 데 적합하다.

이미지 등의 대용량 데이터를 저장하는 것은 가능은 하지만, 애플리케이션 성능에 영향을 미칠 수 있어 적합하지 않다.

그 가능한 방식이 바로 Data 형태로 저장하는 것이다. 기본적으로 UserDefault에는 문자열, 숫자, Data객체 저장만을 지원하기 때문에 이미지를 그대로 저장할 수는 없고, Data 형태로 변환해줘야한다.

하지만, 앞서 말했듯 권장하지 않는 부적합한 방식이라고 한다.

더 나은 이미지 저장 방식은 샌드박스에 이미지를 저장(예를들면 FileManager 활용)하고 그 위치 정보를 UserDefaults에 저장하는 방법이다.

https://cocoacasts.com/ud-9-how-to-save-an-image-in-user-defaults-in-swift

URL 이용 방식

이미지 로딩에서 가장 흔하게 사용하는 방법 중 하나로 URL을 통해 이미지를 불러오는 방식으로

로컬 파일 시스템에서 이미지를 가져오거나, 원격 서버에서 이미지를 로드할 때 주로 사용한다.

  • URLSession을 이용한 비동기 이미지 로딩
  • 필요시에 이미지를 불러와 사용해 메모리 사용에 최적화
  • 이미지 용량 관리에 적합

예시 코드

let imageView = UIImageView()

if let url = URL(string: "https://example.com/image.jpg") {
    URLSession.shared.dataTask(with: url) { data, response, error in
		    guard let data = data,
              response != nil,
              error == nil else { return }
        Task { @MainActor in
            self.imageView.image = UIImage(data: data) ?? UIImage()
        }
    }.resume()
}

Stream 이용 방식

InputStreamOutputStream을 통해 데이터를 부분적으로 읽고 처리하는 방식으로

대용량 이미지 파일이나 네트워크를 통해 이미지를 연속적으로 받아야 할 때 사용될 수 있지만, 많이 사용되지는 않는다.

  • 큰 이미지를 부분적으로 로딩할 수 있어 메모리 효율이 좋다
  • 데이터를 순차적으로 다룰 수 있다

// 예시 만들 수 있게되면 추가할게용..

이미지 캐싱 이용

이미지를 한 번 로드한 후, 캐싱으로 로컬에 저장해 두고 이후 이미지 로드 없이 사용할 수 있는 방식으로 이미지를 네트워크에서 자주 가져와야 하는 경우에 주로 사용한다.

  • 서드파티 라이브러리인 Kingfisher 등을 이용해 더 간단히 사용할 수도 있다

URLCache를 활용한 기본 캐싱

let url = URL(string: "https://example.com/image.jpg")!
let request = URLRequest(url: url)
let cache = URLCache.shared

if let cachedResponse = cache.cachedResponse(for: request) {
    let cachedImage = UIImage(data: cachedResponse.data)
    imageView.image = cachedImage
} else {
    // 캐시에 없으면 네트워크에서 가져오기
    URLSession.shared.dataTask(with: request) { data, response, error in
        if let data = data, let response = response {
            // 응답을 캐시에 저장
            let cachedData = CachedURLResponse(response: response, data: data)
            cache.storeCachedResponse(cachedData, for: request)

            Task { @MainActor in
                imageView.image = UIImage(data: data)
            }
        }
    }.resume()
}

추가 활용 방식 위의 방식들 이외에 이미지를 다루는 추가적인 방식들에 대해 정리했습니다.

Core Graphics 이용

이미지를 직접 다루기 위해 UIImage가 아닌 CGImage를 사용하는 저수준 API를 사용하는 방식으로

주로 이미지의 픽셀 데이터를 직접 다루거나, 특정 효과를 줄 때 사용됩니다.

예시 코드

if let image = UIImage(named: "example") {
    guard let cgImage = image.cgImage else { return }

    let context = CGContext(
                  data: nil,
                  width: cgImage.width, 
                  height: cgImage.height,
                  bitsPerComponent: cgImage.bitsPerComponent, 
		  bytesPerRow: cgImage.bytesPerRow, 
	          space: cgImage.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)!, 
		  bitmapInfo: cgImage.bitmapInfo.rawValue)

    context?.draw(cgImage, in: CGRect(x: 0, y: 0, width: cgImage.width, height: cgImage.height))

    if let newCgImage = context?.makeImage() {
        let resizedImage = UIImage(cgImage: newCgImage)
        // resizedImage 사용
    }
}

UIImagePickerController 이용 방식

사용자가 사진 앱에서 이미지를 선택하거나, 카메라를 통해 이미지를 촬영하는 경우 사용하는 방식으로

갤러리나 카메라를 통해 이미지를 찍고 해당 이미지를 표시한다.

예시 코드

class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {

    func presentImagePicker() {
        let imagePicker = UIImagePickerController()
        imagePicker.delegate = self
        imagePicker.sourceType = .photoLibrary
        present(imagePicker, animated: true, completion: nil)
    }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        if let selectedImage = info[.originalImage] as? UIImage {
            imageView.image = selectedImage
        }
        picker.dismiss(animated: true, completion: nil)
    }
}

Image I/O를 사용한 이미지 최적화 - 리사이징

이미지 파일을 최적화하여 처리할 수 있는 프레임워크로,

큰 이미지를 작은 해상도로 로드할때 사용할 수 있다.

  • 이전에 사용하던 방식인 UIGraphicsImageRenderer 보다 메모리 사용량과 성능 측면에서 우수
  • options를 이용해서 원본 이미지에서 어떻게 조정할지를 지정할 수 있음

예시 코드

// 혹은 이런 방식
// let imageSource = CGImageSourceCreateWithData(data as CFData, nil)
if let url = URL(string: "https://example.com/image.jpg"),
   let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil) {

    let options: [CFString: Any] = [
        kCGImageSourceThumbnailMaxPixelSize: 200,
        kCGImageSourceCreateThumbnailFromImageAlways: true
    ]

    if let thumbnail = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) {
        let image = UIImage(cgImage: thumbnail)
        imageView.image = image
    }
}

참고

https://dev-dain.tistory.com/289

https://ios-development.tistory.com/1569

https://magomercy.com/swift/Core-Graphics%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%A6%AC%EC%82%AC%EC%9D%B4%EC%A7%95-Core-Graphics-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EA%B3%A0%EA%B8%89-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B2%98%EB%A6%AC-%EA%B8%B0%EB%B2%95-f4e418fd