iOS

이미지 다운샘플링 해보기

소재훈 2023. 5. 5. 01:10

이전 포스팅인 WWDC18 — Image and Graphics Best Practice 에서 이미지 메모리의 사용량을 줄이기 위해서는 UIGraphicsImageRenderer나 Image I/O를 사용하는 방법을 소개 받았습니다. 이번에는 저의 실제 사례에 적용시켜보는 시간을 가져보도록 하겠습니다.

먼저 아래와 같은 3.6MB의 파일 사이즈를 가지는 이미지를 사용하였습니다.

위와 같은 이미지를 UIImage로 불러오고 UIImageView로 바로 렌더링 하니 메모리 사용량은 다음과 같았습니다.

위와 같은 3MB 크기의 이미지를 화면에 띄웠더니 다음과 같이 메모리의 사용량이 24.9MB 에서 108MB가 된것을 볼 수 있습니다. 고작 3MB 짜리 이미지를 화면에 띄웠을 뿐인데 왜 이렇게 메모리를 많이 사용하게 되는 것일까요?

그 이유는 메모리의 사용량 = 파일 크기가 아니기 때문입니다. 저희가 보통 Finder에서 이미지를 보았을 때 표시되는 이미지의 크기는 JPEG이나 PNG같은 알고리즘으로 압축되어 있는 이미지의 용량입니다. 이전의 WWDC18 Image and Graphics Best Practice에서도 설명드렸지만 픽셀 하나를 표현하기 위해서 RGB, 그리고 투명도를 표시해야하기 때문에 보통 4byte의 메모리 공간이 필요합니다.

위 이미지의 규격을 보니 4084 × 5105 크기의 이미지였고, 이를 계산해보면,

4,084 × 5,105 × 4byte = 83,395,280 ≈ 83MB로, 위에서 보았던 메모리 증가량과 비슷한 값인 것을 확인할 수 있습니다. 따라서 실제로 메모리 사용량을 줄이기 위해서는 이미지의 픽셀의 개수를 줄여야 하고, 이미지의 픽셀을 줄이는 과정을 다운샘플링(Downsampling) 이라고 합니다.

사실 픽셀의 갯수를 줄이는 것 말고도 메모리의 증가량을 줄이는 또 다른 방법도 있습니다. 하나의 픽셀당 RGBA를 표현해야하기 때문에 4byte가 필요하다고 했는데, 색상을 표현하는 방법을 바꾸어서 하나의 픽셀에 필요한 메모리 양을 줄이는 방법이 있습니다. 그것이 UIGraphicsImageRenderer를 사용하는 방법인데, 이미지를 렌더링 할 때 이미지에 따라서 알아서 색상정보를 수정해줍니다. 예를 들어서, 흑백 이미지를 제공하면 black color만 사용한다는 것을 인식해서 적절한 색 영역을 선택해 메모리를 절약해 줍니다.

하지만 이 방법은 내부적으로 이미지를 한번 복사한다음에 처리를 하기 때문에 메모리에 스파이크가 발생한다는 문제가 있었습니다. 이미지의 크기가 매우 큰 경우에는 (GB 단위의 이미지가 사용될 수도 있습니다) 이러한 메모리 스파이크가 애플리케이션의 종료로 이어질 수 있기 때문에 저는 색 영역의 선택 보다는 픽셀 수를 줄이는 다운샘플링을 선택하였습니다.

다운샘플링을 처리하는 코드를 먼저 소개하겠습니다.

 

이전에 작성해 두었던 글에서도 있는 코드입니다. 이미지의 URL을 통해서 이미지 소스를 가져오고, pointSize 매개변수는 기준이 되는 사이즈 입니다. 이미지의 최대 너비와 높이를 결정하는데 사용되기 때문에 보통 실제로 사용자에게 표현되는 이미지 뷰의 사이즈를 제공하면 될 것 같습니다.

  • imageURL: 이미지 URL을 의미합니다. 웹URL 이나 로컬 이미지 path를 전달하면 됩니다.
  • pointSize: 다운샘플링 될 이미지의 사이즈를 전달합니다. 보통 UIImageView의 프레임 사이즈를 전달하게 됩니다.
  • scale: 다운샘플링 축척계수입니다. 보통 화면과 관련된 배율이 됩니다(보통 @2x 또는 @3x라고 합니다). 따라서 기본값이 UIScreen.main.scale로 설정되어 있는 것을 볼 수 있습니다.

그 다음으로 여러가지 옵션플래그들을 제공하고 있습니다.

  • kCGImageSourceShouldCache 이 플래그를 false로 설정하면 Core Graphic 프레임워크에 이미지 소스에 대한 참조만 생성하고 CGImageSource 객체가 생성될 때 이미지를 디코딩 하지 않겠다는 것을 의미합니다. path를 통해서 액세스 할 수 없는 상황에서는 CGImageSrouceCreateWithData() 이니셜라이저를 사용해서 CGImageSource 객체를 생성할 수 있습니다.
  • kCGImageSourceShouldcacheImmediately 이 플래그는 Core Graphic 프레임워크가 다운 샘플링 프로세스를 시작하는 순간에 이미지를 디코딩 해야함을 나타냅니다.
  • kCGImgaeSourceCreateThumbnailWithTransform 이 플래그를 true로 설정하면 Core Graphic 프레임워크에 다운샘플링된 이미지가 원본 이미지와 동일한 orientation(방향)을 가지기를 원한다는 의미입니다. 이미지를 전달해서 다운샘플링 했는데, 상하좌우 어느 방향으로 반전되거나 회전된 이미지를 얻는다면 당황스럽겠죠? 중요한 플래그입니다.

사용하는 코드는 다음과 같습니다.

 

이미지를 프로젝트 안에 넣어두었기 때문에 Bundle.main을 통해서 접근하였고, 다운샘플링 된 결과를 이미지뷰의 이미지로 넣어주었습니다. 메모리가 얼마나 절약되었을까요?

놀랍게도 메모리 그래프에 거의 변화가 감지되지 않을 만큼 줄어들었습니다. 뭔가 문제가 생긴건가 싶어서 이미지 뷰의 사이즈를 출력해보니 393 × 714 가 나왔고, 차지하는 메모리 공간을 계산해보니

393 × 714 × 4 = 1,122,408 ≈1MB 로 대략 맞는 결과가 나온 것 같습니다. 제 상상이상으로 사용하는 메모리의 양이 줄어드네요 😳

애플리케이션을 개발하면서 컬렉션뷰에 이미지를 띄울 일이 많았는데, 그때마다 이 다운샘플링을 적용하거나, 기가바이트 단위의 이미지를 렌더링 해야할 일이 생기면 그 효과는 더욱더 체감될 것으로 생각됩니다.