ㅇ이번글은 단순히 UIImage, Image를 말하는 게 아닌 더 딥한 정보들을 정리할 계획이다. 먼저
https://developer.apple.com/design/human-interface-guidelines/images
우선 이미지는 두가지 방식으로 구성된다.
래스터 이미지: 픽셀로 이루어져 해상도가 고정되어 있어 확대 축소하면 품질 저하. (JPEG, PNG)
Asset Catalog
보면 1x,2x,3x같이 3개를 넣으라고 나온다. 이게 의미하는 게 뭘까?=> iOS만 보더라도 iphone 8, 14 plus, 16 pro max 등 디스플레이가 각각이 너무 다르다. 이건 해상도가 다르다는 것을 의미한다. 각각의 해상도에 맞는 이미지를 보여줘야 유저가 사용할 때 깨지지 않고 보여줄 수 있다. 원래의 표준해상도에서는 1포인트=1px이다. 요즘 말하는 고해상도는 뭐가 다를까? 한 포인트 안에 px이 더 많다. 즉 고해상도일수록 1포인트에 들어가는 픽셀의 밀도가 더 큰 이미지가 필요한 것이다.
잠깐? 픽셀에 대해 알아보자
해상도에서 ppi라는 것을 들어봤을것이다. 1인치 안에 몇 개의 픽셀이 존재하는지를 말한다. ex) 10 ppi -> 가로 1인치에 10개 세로 1인치에 10개 해서 총 100개의 픽셀. ppi가 더 높으면 표현할 수 있는 점들이 많으니 선명해짐을 의미!
픽셀의 크기는 1, 2,4,8바이트가 있다. 왤캐 다양해? 픽셀을 이루는 포맷에 따라 다르다.
Alpha값만 저장하면 1바이트, 명도+알파 저장 -> 2바이트, SRGB -> 4바이트, wide format -> 8바이트
HIG를 보면
When creating bitmap images, you specify a scale factor which determines the resolution of an image. You can visualize scale factor by considering the density of pixels per point in 2D displays of various resolutions
이미지의 해상도를 결정하는 스케일 팩터를 지정한다고 나와있다. 즉 scale factor = 포인트 안에 픽셀의 밀도에 따른 배율!이다.
HIG문서를 더 보면 1x = 1포인트당 1픽셀을 의미한다. 당연히 고해상도에서는 2:1 또는 3:1 같이 픽셀 밀도가 높다.
HIG의 1x, 2x,3x 비교이다. 3x는 1포인트당 3픽셀이 들어있어 더 선명하게 보여줄 수 있는 것을 체크할 수 있다.
이제 1x, 2x , 3x에셋을 저장하면 앱에는 이렇게 많이 정보가 들어가 있다. 이미지를 보면 또 저걸 기기(mac, iphone, ipad) 별로 따로따로 저장하는 것을 확인할 수 있다. 앱스토어에서 다운로드할때는 해당 정보들을 다 받는 것이 아니라 다운받는 기기에 맞춰 필요한 것만 App slicing과정을 통해 다운받을 수 있다.
어떤 디바이스에 어떤 에셋이 들어가는지는
https://www.ios-resolution.com
이 링크에 디바이스 모두가 나와있다. 1x 만 등록해 놓으면 뭐 이제 16프로 맥스에서는 디자이너가 아무리 이쁘게 해도 개발자가 다 망쳐놓는 거..
벡터 이미지(수학적 좌표+기하학적 형태 기반 이어서 계산을 통해 크기를 자유롭게 조정해도 품질이 같다.): SVG.
그렇다면 어떻게 구별해서 사용해야 하는데?(PNG, JPEG, HEIF SVG)
PNG: 무손실 압축으로 이미지 품질이 유지된다. + 투명도를 지원한다.
JPEG: 손실 압축 방식을 사용해 파일 크기가 작아진다. 투명도지원 X.
HEIF: JPEG보다 더 효율적인 압축 제공. iOS기기의 기본 포맷, Live Photos를 지원한다
HIG문서이다.
- 일반적인 비트맵 혹은 래스터 작업은 인터레이싱 없는 PNG파일포맷으로 해야 한다.
- 24비트 컬러가 필요하지 않은 PNG그래픽은 8비트 색상 팔레트를 사용하는 PNG포맷이다.
- 사진은 JPEG 또는 HEIC이다. 왜? 풍부한 디테일이 들어있으므로 압축해야 함! 너무 커
- 평면 아이콘, UI아이콘 및 고해상도 스케일링 필요한 아트워크는 벡터이미지인 PDF, SVG이다. 그 이유는 크기 조정이 잦기 때문이다.
그렇다면 이 이미지들은 메모리 사용량과 성능에 큰 영향을 주는데 어떻게 적절하게 최적화할 수 있을까?
JPEG, PNG 등의 이미지 파일은 디코딩(GPU에서 읽을 수 있도록)을 통해 이미지 버퍼에 저장된 후 UIImageVIew 또는 SwiftUI에서는 Image뷰를 통해 렌더링(매 초마다 화면에 업데이트되어 보여줌. 60HZ-> 초 60회 번씩 그려주는 거) 된다.
이미지 버퍼의 크기는 뷰의 크기 또는 파일의 용량이 아니라!! 이미지 픽셀 크기와 색영역에 따라 달라진다.
ex) 1024 * 1024 픽셀이고 4바이트의 색상포맷(SRGB)이라면 1024 * 1024 *4 = 4MB
큰 이미지를 처리할 때는 CPU와 메모리를 많이 사용함으로써 자원을 사용해 앱이 버벅거리는 현상이 발생할 수 있고 배터리도 많이 사용된다. -> 작은 크기의 이미지, 필요한 색 영영을 사용하여 자원을 절약해야 한다.
이미지 성능 최적화
1. 적절한 이미지 크기 사용.
필요 이상으로 고해상도 이미지를 사용하지 않으면 된다.
UIGraphicsImageRenderer -> 자동으로 최적의 이미지 렌더 포맷을 선택해 준다. 이론상 75% 이상의 메모리 절약 효과가 있다고 한다.
let image = UIImage(named: "photo")!
let imageSize: CGSize = imageView.frame.size
let render = UIGraphicsImageRenderer(size: imageSize)
let renderImage = render.image { _ in
image.draw(in: CGRect(origin: .zero, size: imageSize))
}
extension UIImage {
// 불러온 이미지 사이즈 변경 (Compact 버전)
func resized(to size: CGSize) -> UIImage {
let imageSize = size
return UIGraphicsImageRenderer(size: imageSize).image { _ in
draw(in: CGRect(origin: .zero, size: imageSize))
}
}
}
let image = UIImage(named: "photo")!
let imageSize: CGSize = imageView.frame.size
let renderImage = image.resized(to: imageSize)
하지만 이. 방법의 경우 draw에서 CPU비용을 사용하게 된다. 그래서 큰 해상도 이미지 사용하거나 다시 그릴 때는 앱성능에 문제가 발생한다.
그래서 다운샘플링이 나왔다.
위의 이미지 프로세스를 보면 이미지 로드 후. Data Buffer 먼저 로드되고 디코딩된 데이터(Image buffer)가 복사되어 보이는 데이터(FrameBuffer)만큼 크기를 조절해 준다. GPU에서 이미지 데이터를 읽는 디코딩 과정에서 메모리가 사용되는데 만약 필요한 크기만큼 데이터를 미리 축소 후 썸네일로 캡처하여 불필요한 DataBuffer를 제거한 채로 디코딩 작업을 하면 메모리를 절약할 수 있다.
디코딩에서의 메모리 비용을 줄이는 방법이다.
func downsampleImage(at imageURL: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage {
let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOptions)!
let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
let downsampleOptions = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels
] as CFDictionary
let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions)!
return UIImage(cgImage: downsampledImage)
}
공식문서의 코드이다.
kCGImageSourceShouldCache: 디코딩된 이미지를 캐시 할지 여부를 나타냄. 당연히 디코딩되기 전 값이 필요하므로 초기에 false로 한다.
CGImageSourceCreateWithURL: URL로 지정된 위치에서 읽는 이미지 소스를 만든다.
kCGImageSourceCreateThumbnailFromImageAlways: 항상 축소판 이미지를 만들지의 여부
kCGImageSourceShouldCacheImmediately: 이미지 생성 시 디코딩 및 캐싱이 발생하는지 여부를 나타내는 불값.
kCGImageSourceCreateThumbnailWithTransform: 이미지 방향 및 종횡비 일치하도록 썸네일 이미지 회전 및 크기 조정할지 여부
kCGImageSourceThumbnailMaxPixelSize: 축소판 이미지의 최대너비와 높이(픽셀단위)
CGImageSourceCreateThumbnailAtIndex: 지정된 인덱스에 이미지 썸네일을 만들어 CGImage return
2. 비동기 이미지 로딩
비동기로 이미지 로드하여 메인스레드 작업 부하 줄일 수 있다.
3. 이미지 캐싱
반복적으로 사용되는 이미지는 캐싱하여 네트워크 요청 및 디코딩 비용 줄일 수 있다.
'iOS' 카테고리의 다른 글
Alamofire error code handling (2) | 2024.12.17 |
---|---|
CoreLocation과 Battery의 관계 (1) | 2024.04.21 |
CoreLocation & MapKit (1) | 2024.04.19 |
Uniform type Identifiers (1) | 2024.04.18 |
Coremotion2편- 걷기데이터 & HealthKit (0) | 2024.04.17 |