사용자가 선택한 노래를 저장하는 단위인 카드로 만드는 개발을 하고 있었습니다.
디자이너님과 상의한 결과, 카드의 하단에는 노래와 관련한 정보들이 들어가기에 상단의 앨범커버의 최하단 중간의 색상을 카드의 전체적인 색상으로 결정하기로 했습니다.
하지만, 테스트 플라이트로 테스트를 해주신 분들이 색상을 고를 수 있으면 좋겠다는 피드백을 주셨습니다.
기존의 색상 플레이트에서 색상을 뽑아 오는 것이 아닌, 앨범 커버 위에서 색상을 뽑으면 좋겠다고 의견이 모아져 Custom Color Picker를 만들게 되었습니다.
해결 과정
ZStack{
// 앨범커버
Image(uiImage: viewModel.card.albumArtUIImage)
// Color Picker 활성화 버튼
Button(action: { dragPointerIsHidden.toggle() }){
// ...
}
// Color Picker
if !dragPointerIsHidden {
CustomColorPicker()
.offset(viewModel.draggedOffset)
}
}
.gesture(
DragGesture()
.onChanged { gesture in
//...
viewModel.getColorFromImagePixel()
}
)
우선, DragGesture을 통해서 앨범 커버 위의 Color Picker를 움직일 수 있게 하였습니다.
그 이후, Color Picker의 위치에 따라 앨범 커버의 색상을 분석하여 적용합니다.
1. CGContext와 CGImage 만들기
guard let cgImage = image.cgImage else { return }
let width = cgImage.width
let height = cgImage.height
let bitmapInfo = cgImage.bitmapInfo.rawValue
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bytesPerPixel = 4 // RGB+alpha
let bytesPerRow = bytesPerPixel * width
guard let context = CGContext(data: nil, width: width, height: height,
bitsPerComponent: 8, bytesPerRow: bytesPerRow,
space: colorSpace, bitmapInfo: bitmapInfo) else { return }
- CGImage: 비트맵 이미지 또는 이미지 마스크
- 이미지 마스크: 이미지나 그래픽 요소의 특정 부분을 불투명 또는 투명하게 만들어주는 기술
- 비트맵 이미지: 사각형 격자로 된 작은 점(픽셀)들이 모여서 표시된 이미지
- CGContext: 2차원 그리기 도구
- bitsPerComponent: 각 컬러 채널의 비트수32bits RGBA colorSpace를 사용하기 때문에, 각각의 색 8bits를 사용
- CGBitmapInfo: 비트맵이 알파 채널을 포함해야 하는지 여부, 픽셀에서 알파 채널의 상대적 위치 및 픽셀 구성 요소가 부동 소수점인지 정수 값인지에 대한 정보를 지정하는 상수입니다.
- bytesPerRow: 각 행별 바이트 수 [ 84width/8 = 4*width]
- data: 개별 pixel 값에 접근하여 값을 사용할 경우 사용
2. 이미지의 픽셀 데이터 뽑아내기
let pixelData = context.data?.assumingMemoryBound(to: UInt8.self)
assumingMemoryBound: 메모리가 지정된 유형에 이미 바인딩되어 있다고 가정하고 이 포인터가 참조하는 메모리에 입력된 포인터를 반환. → UInt8 1차원 배열
3. 픽셀에서 컬러 뽑아내기
let offset = bytesPerRow * y + bytesPerPixel * x
let r = Double((pixelData?[offset])!)/225
let g = Double((pixelData?[offset + 1])!)/225
let b = Double((pixelData?[offset + 2])!)/225
전체코드
public func getColorFromImagePixel(){
let image = card.albumArtUIImage
guard let cgImage = image.cgImage else {
card.cardColor = .blackColor
return
}
let width = cgImage.width
let height = cgImage.height
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bytesPerPixel = 4
let bytesPerRow = bytesPerPixel * width
let bitmapInfo = cgImage.bitmapInfo.rawValue
guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) else {
card.cardColor = .blackColor
return
}
let rect = CGRect(x: 0, y: 0, width: width, height: height)
context.draw(cgImage, in: rect)
let x = Int(Int(draggedOffset.width+175)*width/350)
let y = Int(Int(draggedOffset.height+175)*height/350)+40
if(y >= 980 || y <= 10){
draggedOffset = .zero
accumulatedOffset = .zero
return
}
let pixelData = context.data?.assumingMemoryBound(to: UInt8.self)
let offset = bytesPerRow * y + bytesPerPixel * x
let r = Double((pixelData?[offset])!)/225
let g = Double((pixelData?[offset + 1])!)/225
let b = Double((pixelData?[offset + 2])!)/225
card.cardColor = Color(red: r, green: g, blue: b)
}
결과물
'iOS' 카테고리의 다른 글
백그라운드 상태에서, 알람 소리 켜기 (0) | 2024.03.20 |
---|---|
배경색에 따른, 글자 색 변환 (0) | 2024.03.19 |
사용자 경험을 위한 최소버전 설정 (0) | 2024.03.19 |
SwiftUI - 토스페이먼트, 결제 동의 관련 (0) | 2024.03.19 |
이미지 리사이징으로, 저장용량 줄이기 (0) | 2024.03.19 |