티스토리 뷰
728x90
반응형
1. 이미지에서 평균 색상 추출
extension UIImage {
// https://www.rudrank.com/exploring-core-graphics-extract-prominent-unique-colors-uiimage/
/// 이미지에서 대표 색상 추출 Extracts the most prominent and unique colors from the image.
///
/// - Parameter numberOfColors: The number of prominent colors to extract (default is 1).
/// - Returns: An array of UIColors representing the prominent colors.
func extractColors(numberOfColors: Int = 1) throws -> [UIColor] {
// Ensure the image has a CGImage
guard let _ = self.cgImage else {
throw NSError(domain: "Invalid image", code: 0, userInfo: nil)
}
let size = CGSize(width: 100, height: 100 * self.size.height / self.size.width)
UIGraphicsBeginImageContext(size)
self.draw(in: CGRect(origin: .zero, size: size))
guard let resizedImage = UIGraphicsGetImageFromCurrentImageContext() else {
UIGraphicsEndImageContext()
throw NSError(domain: "Failed to resize image", code: 0, userInfo: nil)
}
UIGraphicsEndImageContext()
guard let inputCGImage = resizedImage.cgImage else {
throw NSError(domain: "Invalid resized image", code: 0, userInfo: nil)
}
let width = inputCGImage.width
let height = inputCGImage.height
let bytesPerPixel = 4
let bytesPerRow = bytesPerPixel * width
let bitsPerComponent = 8
guard let data = calloc(height * width, MemoryLayout<UInt32>.size) else {
throw NSError(domain: "Failed to allocate memory", code: 0, userInfo: nil)
}
defer { free(data) }
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue
guard let context = CGContext(data: data, width: width, height: height,
bitsPerComponent: bitsPerComponent,
bytesPerRow: bytesPerRow,
space: colorSpace,
bitmapInfo: bitmapInfo) else {
throw NSError(domain: "Failed to create CGContext", code: 0, userInfo: nil)
}
context.draw(inputCGImage, in: CGRect(x: 0, y: 0, width: width, height: height))
let pixelBuffer = data.bindMemory(to: UInt8.self, capacity: width * height * bytesPerPixel)
var pixelData = [PixelData]()
for y in 0..<height {
for x in 0..<width {
let offset = ((width * y) + x) * bytesPerPixel
let r = pixelBuffer[offset]
let g = pixelBuffer[offset + 1]
let b = pixelBuffer[offset + 2]
pixelData.append(PixelData(red: Double(r), green: Double(g), blue: Double(b)))
}
}
let clusters = kMeansCluster(pixels: pixelData, k: numberOfColors)
let colors = clusters.map { cluster -> UIColor in
UIColor(red: CGFloat(cluster.center.red / 255.0),
green: CGFloat(cluster.center.green / 255.0),
blue: CGFloat(cluster.center.blue / 255.0),
alpha: 1.0)
}
return colors
}
private struct PixelData {
let red: Double
let green: Double
let blue: Double
}
private struct Cluster {
var center: PixelData
var points: [PixelData]
}
private func kMeansCluster(pixels: [PixelData], k: Int, maxIterations: Int = 10) -> [Cluster] {
var clusters = [Cluster]()
for _ in 0..<k {
if let randomPixel = pixels.randomElement() {
clusters.append(Cluster(center: randomPixel, points: []))
}
}
for _ in 0..<maxIterations {
for clusterIndex in 0..<clusters.count {
clusters[clusterIndex].points.removeAll()
}
for pixel in pixels {
var minDistance = Double.greatestFiniteMagnitude
var closestClusterIndex = 0
for (index, cluster) in clusters.enumerated() {
let distance = euclideanDistance(pixel1: pixel, pixel2: cluster.center)
if distance < minDistance {
minDistance = distance
closestClusterIndex = index
}
}
clusters[closestClusterIndex].points.append(pixel)
}
for clusterIndex in 0..<clusters.count {
let cluster = clusters[clusterIndex]
if cluster.points.isEmpty { continue }
let sum = cluster.points.reduce(PixelData(red: 0, green: 0, blue: 0)) { (result, pixel) -> PixelData in
return PixelData(red: result.red + pixel.red, green: result.green + pixel.green, blue: result.blue + pixel.blue)
}
let count = Double(cluster.points.count)
clusters[clusterIndex].center = PixelData(red: sum.red / count, green: sum.green / count, blue: sum.blue / count)
}
}
return clusters
}
private func euclideanDistance(pixel1: PixelData, pixel2: PixelData) -> Double {
let dr = pixel1.red - pixel2.red
let dg = pixel1.green - pixel2.green
let db = pixel1.blue - pixel2.blue
return sqrt(dr * dr + dg * dg + db * db)
}
}
2. 사용하는 쪽 뷰
/// 색상 추출 뷰
struct ProminentColorsView: View {
@State private var colors: [UIColor] = []
@State private var errorMessage: String?
private let image: UIImage = .example
var body: some View {
VStack {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
.cornerRadius(16)
.padding(.horizontal)
if !colors.isEmpty {
ColorPaletteView(colors: colors)
}
if let errorMessage = errorMessage {
Text("Error: \(errorMessage)")
.foregroundColor(.red)
}
}
.task {
do {
colors = try image.extractColors(numberOfColors: 8)
} catch {
errorMessage = error.localizedDescription
}
}
}
}
/// 색상 팔레트 뷰
struct ColorPaletteView: View {
let colors: [UIColor]
let columns = [GridItem(.flexible()), GridItem(.flexible())]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: 16) {
ForEach(0..<colors.count, id: \.self) { index in
let uiColor = colors[index]
VStack {
Rectangle()
.fill(Color(uiColor))
.frame(height: 60)
.cornerRadius(12)
Text(uiColor.toHexString())
}
.font(.caption)
.foregroundColor(.primary)
}
}
.padding()
}
}
}
3. 색상 헥사 코드 문자열 형식
extension UIColor {
/// 헥사 코드 문자열로 변환
func toHexString() -> String {
var rFloat: CGFloat = 0
var gFloat: CGFloat = 0
var bFloat: CGFloat = 0
var aFloat: CGFloat = 0
self.getRed(&rFloat, green: &gFloat, blue: &bFloat, alpha: &aFloat)
let rInt = Int(rFloat * 255)
let gInt = Int(gFloat * 255)
let bInt = Int(bFloat * 255)
return String(format: "#%02X%02X%02X", rInt, gInt, bInt)
}
}
iOS
Swift
Xcode
SwiftUI
Objective-C
SwiftUI
Extract
UIColor
UIImage
UIGraphicsBeginImageContext
CGColorSpaceCreateDeviceRGB
k-means 클러스터링
Clustering
CGImageAlphaInfo
premultipliedLast
bindMemory
hex
pixel
728x90
반응형
'iOS SwiftUI' 카테고리의 다른 글
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- TabBar
- 인디케이터
- 아이오에스
- 테이블뷰
- swiftUI
- Authorization
- localizing
- ios
- indicator
- SKPayment
- 리젝
- permission
- Xcode
- 다국어
- SWIFT
- localizable
- TabView
- Reject
- 프로그레스
- SKProductsRequestDelegate
- custom segment
- 엑스코드
- 현지화
- 로컬라이징
- Language
- AppStore
- presentationcompactadaptation
- picker
- 심사
- 스위프트
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
글 보관함