iyOmSd/Title: Swift

[Swift] Image Resize

냄수 2021. 5. 29. 19:08
반응형

이미지가 커서 메모리를 많이 잡아먹다보면

메모리부족으로 앱이 죽어버리는 경우가 생기죠..!

이러한 현상을 줄이고자 이미지 사이즈를 줄여서 다시 만들어내는데

그 작업을 해볼까해요

 

이미지작업을 위해서 사용할 클래스부터 알아볼게요

UIGraphicsBeginImageContext 와 UIGraphicsImageRenderer

가 있는데요둘의 차이는 구식과 신식의 차이에요 

 

UIGraphicsBeginImageContext

iOS2+

SRGB 포맷

 

스택을 사용해서 처리하는 것 같더라구요

UIGraphicsBeginImageContext을 이용해서 context를 생성해주고

원하는 처리를 해주고

UIGraphicsGetImageFromCurrentImageContext를 이용해서

context에 적용된이용해서 이미지를 가져오고

UIGraphicsEndImageContext를 이용해서 스택상단에 있는 현재 context를 제거하는 방식으로

사용해요

 

 

UIGraphicsImageRenderer

iOS10+

Wide 포맷

 

UIGraphicsImageRenderer를 생성하고

클로져 방식으로 모든 처리를 적용시켜주는 방식으로 사용해요

과거의 방식보다 간단해보이죠

WWDC18에서

UIGraphicsImageRenderer를 사용하라고 권장하고 있어요

이미지처리를 검색하다보면 위의 단어들이 모두 보일텐데

최신식으로 처리하는게 좋을 것 같네요!

 

 

 

코드로 구현하면서 살펴볼까요~~

extension UIImage {
    func resize(newWidth: CGFloat) -> UIImage {
        let scale = newWidth / self.size.width
        let newHeight = self.size.height * scale

        let size = CGSize(width: newWidth, height: newHeight)
        let render = UIGraphicsImageRenderer(size: size)
        let renderImage = render.image { context in
            self.draw(in: CGRect(origin: .zero, size: size))
        }
        
        print("화면 배율: \(UIScreen.main.scale)")// 배수
        print("origin: \(self), resize: \(renderImage)")
        printDataSize(renderImage)
        return renderImage
    }
}

파라미터로 새롭게 적용할 가로길이를 받아서

원본이미지와 비율을 비교해서 세로길이도 적용시켜주는 방법을 사용했어요

 

코드가 어렵지않아요

UIGraphicsImageRenderer객체를 생성해주고

image()함수를 통해서 이미지에 접근해서 원하는 작업을 해주고

(변경된 size로 수정하는 코드)

반환해주면 끝이에요

 

제대로 이미지크기가 줄어들었나 확인해볼까요?

 

UI는

6mb, 9mb, scale(1x 2x 3x)이미지를 asset으로 넣어뒀고

클릭하면 해당 이미지가 나타나는 동작이구요

3x를 사용하고

엄청 큰 용량의 데이터였는데

확줄어든 크기가 보이시나요?

 

이미지 처리 프로세스를 조금 보자면

UIImage는 단지 데이터타입이라고 소개되구요

이 이미지를 디코딩해서 이미지를 뷰에 보여주도록 렌더를 하고 그 렌더된 결과를 눈으로 볼 수 있죠

이때 디코딩을 통해 이미지를 만들때 많은 메모리가 쓰이고 많은 CPU가 사용되는 작업이라고해요

 

위의 코드에서는

let renderImage = render.image { context in
    self.draw(in: CGRect(origin: .zero, size: size))
}

render.image부분과

draw부분이 많은 CPU를 사용하게돼요

 

큰 이미지를 사용할 경우 디코딩하면서

CPU를 많이사용하고

계속 이미지를 보여주기위해서 반복적인 렌더링을 할수도 있고

큰 메모리할당

뷰 크기가아니라 이미지크기에 비례한 작업을해서(뷰크기가 300x300이여도 이미지크기가1000x300이면 그대로사용)

 

WWDC에서 메모리를 절약하도록 대안을 주죠

downSampling을 통해 이미지사이즈를 줄이도록말이죠

 

얼마나 메모리가 차이나길레 이런작업을 해야하나..??

파일용량과 메모리사용량은 달라요

9mb이미지파일을 이미지뷰에 디코딩하면 140mb가 되는것처럼요...

 

 

이미지 resize 전후 이미지가 차지하는 메모리공간의 차이가 보이시나요?? 좀 커요.. ㅎㅎ

이러한 고용량의 이미지가 더쌓인다면... 메모리부족으로 앱이 펑....!

 

지금은 큰이미지를 작은 이미지뷰에 넣기때문에 줄어들었지만

반대로 작은이미지를 사용해서 큰이미지뷰에 넣는다면 용량이 커질수도 있어요

크기만 잘 정해준다면 많은 문제없이 사용할 수 있는 간단한 방법이죠

 

 

 

.

.

.

.

다른 방법으로는 위에서 언급한

downSampling을 하는 방법이에요

이미지자체의 픽셀을 줄여서 용량을 감소시키는거죠

// 원하는 해상도에 맞게 조절
func downSample1(scale: CGFloat) -> UIImage {
    let imageSourceOption = [kCGImageSourceShouldCache: false] as CFDictionary
    let data = self.pngData()! as CFData
    let imageSource = CGImageSourceCreateWithData(data, nil)!
    let maxPixel = max(self.size.width, self.size.height) * scale
    let downSampleOptions = [
        kCGImageSourceCreateThumbnailFromImageAlways: true,
        kCGImageSourceShouldCacheImmediately: true,
        kCGImageSourceCreateThumbnailWithTransform: true,
        kCGImageSourceThumbnailMaxPixelSize: maxPixel
    ] as CFDictionary

    let downSampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downSampleOptions)!

    let newImage = UIImage(cgImage: downSampledImage)
    printDataSize(newImage)
    return newImage
}


// 이미지뷰 크기에 맞게 조절
func downSample2(size: CGSize, scale: CGFloat = UIScreen.main.scale) -> UIImage {
    let imageSourceOption = [kCGImageSourceShouldCache: false] as CFDictionary
    let data = self.pngData()! as CFData
    let imageSource = CGImageSourceCreateWithData(data, imageSourceOption)!

    let maxPixel = max(size.width, size.height) * scale
    let downSampleOptions = [
        kCGImageSourceCreateThumbnailFromImageAlways: true,
        kCGImageSourceShouldCacheImmediately: true,
        kCGImageSourceCreateThumbnailWithTransform: true,
        kCGImageSourceThumbnailMaxPixelSize: maxPixel
    ] as CFDictionary

    let downSampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downSampleOptions)!

    let newImage = UIImage(cgImage: downSampledImage)
    printDataSize(newImage)
    return newImage
}

파라미터로 scale을 받아서 줄이려고하는 비율만큼 이미지를 줄이는 방법 1

파라미터로 size(이미지뷰 크기)와 scale(기기의 해상도 1x,2x,3x)를 받아서 이미지 크기를 조절하는 방법2

를 구현했어요

 

크기를 처리하는부분만다르고 나머지는 같은코드에요

 

좀어려운 클래스들이 많아요

CF접두사는 Core Foundation에 속한 데이터타입을 사용하는거에요

이 방법은 좀더 low level단계에서 컨트롤 할 수 있는 방법이죠

low로 갈수록 복잡해보이고 어렵지만 상세하게 컨트롤하다는점...

 

문서를 번역해보긴했는데 이상할 수도있어요..

 

kCGImageSourceShouldCache

이미지를 디코딩된 형태로 캐쉬해야하는지 여부

default 32비트에서는 false 64비트에서는 true

 

CGImageSourceCreateWithData

Core Foundation에서 읽을 수 있는 이미지 소스를 생성해요(URL은 뒤에 Data가아닌 URL함수로 가능해요)(수정할 이미지생성)

 

kCGImageSourceCreateThumbnailFromImageAlways

이미지 소스파일에 썸네일이 있는경우 전체이미지에서 썸네일을 작성해야하는지 여부

 

kCGImageSourceCreateThumbnailWithTransform

전체이미지의 방향 및 죙횡비에 따라 썸네일을 회전하고 스케일해야하는지 여부

 

kCGImageSourceThumbnailMaxPixelSize

썸네일의 가로세로 픽셀의 최댓값 지정

 

CGImageSourceCreateThumbnailAtIndex

이미지소스와 지정한 위치에 있는 이미지를 이용해서 이미지 썸네일 생성(주어진 이미지에 옵션을 적용해서 CGImage생성)

 

 

위에서한것과 지금 한것이 어떤차이나면

왼쪽: resize. 가운데: downSampling1(0.1), 오른쪽: downSampling2

 

가운데는 이미지크기를 0.1만큼 줄여버려서 크게 화질이깨진 상태구요

결과물인 이미지만 보면 downSampling도 resize와 비슷한 성능을 보여주는것 같네요

 

resize vs downSampling

resize와 downSampling의 내부적인 차이로는

resize방법이 크기조정과 변환과정에서 많은 비용이 든다고해요

 

downSampling방법은 low level이라그런가 좀 더 빠른 API라고하네요

전처리로 줄어든 데이터를 가지고 이미지를 만드는 작업을해서 더 적은 메모리를 사용하는 장점이 있다고하네요

 

아주 성능이 중요하다! 라고 생각하면 고려해볼 필요가 있겠네요

 

여기까지가 이미지 resize하는 방법이구요

아래는 이미지 decode를 스크롤하는 곳에서 할 때 발생할 수 있는 문제인데요

간단하게 언급하고 가려구요

알아두면 좋을 것같아요

 

코드를 따라하시면서 앱을 빌드해보면

CPU사용량이 엄청올라가는걸 볼 수 있을텐데요

이러한 작업을

스크롤이 있는 곳에서 한다면... 버벅버벅... 거릴수도있겠네요

 

이런현상을 방지하고자

prefetch API가 잇죠

또, 이미지처리를 다른 스레드로 넘겨서 작업하도록 주는거에요

여기서 스레드작업을할 때 

스크롤할때마다 DispatchQueue.global을 통해 작업을 처리한다면

스레드가 계속생성되고...

폭발할지도 모르죠 ㅎㅎ

 

이러한 경우엔 DispatchQueue를 생성해서 한 곳에서만 처리하도록 하는게 좋은 방식이라고 하네요

 

 

 

 

 

 

참고자료: WWDC18 (iOS Memory Deep Dive, Image and Graphics Best Practices)

 

 

 

 

 

 

 

반응형