(_ f: @escaping (T) -> U) -> (T) -> U {
10 | var cache = [T : U]()
11 |
12 | return { key in
13 | var value = cache[key]
14 | if value == nil {
15 | value = f(key)
16 | cache[key] = value
17 | }
18 | return value!
19 | }
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/ImageColorPicker.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "alerttoast",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/elai950/AlertToast.git",
7 | "state" : {
8 | "branch" : "master",
9 | "revision" : "a437862bb6605080a5816e866cbd4ac8c8657b49"
10 | }
11 | },
12 | {
13 | "identity" : "permissionskit",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/sparrowcode/PermissionsKit",
16 | "state" : {
17 | "branch" : "main",
18 | "revision" : "976d46ddf8e6a30118ad278abc9c4f936c919ce4"
19 | }
20 | }
21 | ],
22 | "version" : 2
23 | }
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ImageColorPicker
2 | This project shows how grab colors from an image
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/ImageColorPicker/Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Extensions.swift
3 | // ImageColorPicker
4 | //
5 | // Created by Abdullah Kardas on 3.10.2022.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 | import SwiftUI
11 |
12 | extension UIColor {
13 | var hexString: String {
14 | guard let components = self.cgColor.components else { return "" }
15 |
16 | let red = Float(components[0])
17 | let green = Float(components[1])
18 | let blue = Float(components[2])
19 | return String(format: "#%02lX%02lX%02lX", lroundf(red * 255), lroundf(green * 255), lroundf(blue * 255))
20 | }
21 | }
22 |
23 | extension View {
24 | func onTapWithBounce(onClick:@escaping () -> Void) -> some View {
25 | modifier(bounce(onClick: onClick))
26 | }
27 | }
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/ImageColorPicker/Modifiers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Modifiers.swift
3 | // ImageColorPicker
4 | //
5 | // Created by Abdullah Kardas on 3.10.2022.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | struct bounce:ViewModifier {
12 | let onClick:() -> Void
13 | func body(content: Content) -> some View {
14 | Button {
15 |
16 | onClick()
17 | } label: {
18 | content
19 | }.buttonStyle(BounceStyle())
20 | }
21 | }
22 | struct BounceStyle: ButtonStyle {
23 |
24 | func makeBody(configuration: Self.Configuration) -> some View {
25 |
26 | return configuration.label
27 | .scaleEffect(configuration.isPressed ? CGFloat(0.85) : 1.0)
28 | .animation(.default, value: configuration.isPressed)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/ImageColorPicker/DominantColor/INVector3SwiftExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // INVector3SwiftExtensions.swift
3 | // DominantColor
4 | //
5 | // Created by Indragie on 12/24/14.
6 | // Copyright (c) 2014 Indragie Karunaratne. All rights reserved.
7 | //
8 |
9 | import simd
10 |
11 | extension simd_float3 {
12 | func unpack() -> (Float, Float, Float) {
13 | return (x, y, z)
14 | }
15 |
16 | static var identity: simd_float3 {
17 | return simd_float3(0, 0, 0)
18 | }
19 |
20 | static func +(lhs: simd_float3, rhs: simd_float3) -> simd_float3 {
21 | return simd_float3(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z)
22 | }
23 |
24 | static func /(lhs: simd_float3, rhs: Float) -> simd_float3 {
25 | return simd_float3(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs)
26 | }
27 |
28 | static func /(lhs: simd_float3, rhs: Int) -> simd_float3 {
29 | return lhs / Float(rhs)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/ImageColorPicker/ImagePickerLibrary/SUImagePickerView.swift:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // SUImagePickerView.swift
4 | // SUImagePickerView
5 | //
6 | // Created by Karthick Selvaraj on 02/05/20.
7 | // Copyright © 2020 Karthick Selvaraj. All rights reserved.
8 | //
9 | import SwiftUI
10 | import UIKit
11 |
12 | struct SUImagePickerView: UIViewControllerRepresentable {
13 |
14 | var sourceType: UIImagePickerController.SourceType = .photoLibrary
15 | @Binding var image: UIImage?
16 | @Binding var isPresented: Bool
17 |
18 | func makeCoordinator() -> ImagePickerViewCoordinator {
19 | return ImagePickerViewCoordinator(image: $image, isPresented: $isPresented)
20 | }
21 |
22 | func makeUIViewController(context: Context) -> UIImagePickerController {
23 | let pickerController = UIImagePickerController()
24 | pickerController.sourceType = sourceType
25 | pickerController.delegate = context.coordinator
26 | return pickerController
27 | }
28 |
29 | func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
30 | // Nothing to update here
31 | }
32 |
33 | }
34 |
35 | class ImagePickerViewCoordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
36 |
37 | @Binding var image: UIImage?
38 | @Binding var isPresented: Bool
39 |
40 | init(image: Binding, isPresented: Binding) {
41 | self._image = image
42 | self._isPresented = isPresented
43 | }
44 |
45 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
46 | if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
47 | self.image = image
48 | }
49 | self.isPresented = false
50 | }
51 |
52 | func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
53 | self.isPresented = false
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/ImageColorPicker/DominantColor/KMeans.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KMeans.swift
3 | // DominantColor
4 | //
5 | // Created by Indragie on 12/20/14.
6 | // Copyright (c) 2014 Indragie Karunaratne. All rights reserved.
7 | //
8 |
9 | import Darwin
10 | import GameKit
11 |
12 | // Represents a type that can be clustered using the k-means clustering
13 | // algorithm.
14 | protocol ClusteredType {
15 | // Used to compute average values to determine the cluster centroids.
16 | static func +(lhs: Self, rhs: Self) -> Self
17 | static func /(lhs: Self, rhs: Int) -> Self
18 |
19 | // Identity value such that x + identity = x. Typically the 0 vector.
20 | static var identity: Self { get }
21 | }
22 |
23 | struct Cluster {
24 | let centroid: T
25 | let size: Int
26 | }
27 |
28 | // k-means clustering algorithm from
29 | // http://users.eecs.northwestern.edu/~wkliao/Kmeans/
30 |
31 | func kmeans(
32 | _ points: [T],
33 | k: Int,
34 | seed: UInt64,
35 | distance: ((T, T) -> Float),
36 | threshold: Float = 0.0001
37 | ) -> [Cluster] {
38 |
39 | let n = points.count
40 | assert(k <= n, "k cannot be larger than the total number of points")
41 |
42 | var centroids = points.randomValues(k, seed: seed)
43 | var memberships = [Int](repeating: -1, count: n)
44 | var clusterSizes = [Int](repeating: 0, count: k)
45 |
46 | var error: Float = 0
47 | var previousError: Float = 0
48 |
49 | repeat {
50 | error = 0
51 | var newCentroids = [T](repeating: T.identity, count: k)
52 | var newClusterSizes = [Int](repeating: 0, count: k)
53 |
54 | for i in 0.. 0 {
67 | centroids[i] = newCentroids[i] / size
68 | }
69 | }
70 |
71 | clusterSizes = newClusterSizes
72 | previousError = error
73 | } while abs(error - previousError) > threshold
74 |
75 | return zip(centroids, clusterSizes).map { Cluster(centroid: $0, size: $1) }
76 | }
77 |
78 | private func findNearestCluster(_ point: T, centroids: [T], k: Int, distance: (T, T) -> Float) -> Int {
79 | var minDistance = Float.infinity
80 | var clusterIndex = 0
81 | for i in 0.. [Element] {
93 | if self.isEmpty {
94 | return self
95 | }
96 | let rs = GKMersenneTwisterRandomSource()
97 | rs.seed = seed
98 |
99 | let rd = GKRandomDistribution(randomSource: rs, lowestValue: 0, highestValue: self.count - 1)
100 |
101 | var indices = [Int]()
102 | indices.reserveCapacity(num)
103 |
104 | for _ in 0.. [NSColor] {
40 | let image = cgImage(forProposedRect: nil, context: nil, hints: nil)!
41 | let colors = dominantColorsInImage(image, maxSampledPixels: maxSampledPixels, accuracy: accuracy, seed: seed, memoizeConversions: memoizeConversions)
42 | return colors.map { NSColor(cgColor: $0)! }
43 | }
44 | }
45 |
46 | #elseif os(iOS)
47 | import UIKit
48 |
49 | public extension UIImage {
50 | /**
51 | Computes the dominant colors in the receiver
52 |
53 | - parameter maxSampledPixels: Maximum number of pixels to sample in the image. If
54 | the total number of pixels in the image exceeds this
55 | value, it will be downsampled to meet the constraint.
56 | - parameter accuracy: Level of accuracy to use when grouping similar colors.
57 | Higher accuracy will come with a performance tradeoff.
58 | - parameter seed: Seed to use when choosing the initial points for grouping
59 | of similar colors. The same seed is guaranteed to return
60 | the same colors every time.
61 | - parameter memoizeConversions: Whether to memoize conversions from RGB to the LAB color
62 | space (used for grouping similar colors). Memoization
63 | will only yield better performance for large values of
64 | `maxSampledPixels` in images that are primarily comprised
65 | of flat colors. If this information about the image is
66 | not known beforehand, it is best to not memoize.
67 |
68 | - returns: A list of dominant colors in the image sorted from most dominant to
69 | least dominant.
70 | */
71 |
72 | func dominantColors(
73 | _ maxSampledPixels: Int = DefaultParameterValues.maxSampledPixels,
74 | accuracy: GroupingAccuracy = DefaultParameterValues.accuracy,
75 | seed: UInt64 = DefaultParameterValues.seed,
76 | memoizeConversions: Bool = DefaultParameterValues.memoizeConversions
77 | ) -> [UIColor] {
78 | if let CGImage = self.cgImage {
79 | let colors = dominantColorsInImage(CGImage, maxSampledPixels: maxSampledPixels, accuracy: accuracy, seed: seed, memoizeConversions: memoizeConversions)
80 | return colors.map { UIColor(cgColor: $0) }
81 | } else {
82 | return []
83 | }
84 | }
85 | }
86 |
87 | #endif
88 |
89 |
--------------------------------------------------------------------------------
/ImageColorPicker/DominantColor/ColorDifference.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ColorDifference.swift
3 | // DominantColor
4 | //
5 | // Created by Indragie on 12/22/14.
6 | // Copyright (c) 2014 Indragie Karunaratne. All rights reserved.
7 | //
8 |
9 | import simd
10 |
11 | @inlinable func SIMDMathDegreesToRadians(_ degrees: Float) -> Float {
12 | return degrees * (Float.pi / 180.0)
13 | }
14 |
15 | @inlinable func SIMDMathRadiansToDegrees(_ radians: Float) -> Float {
16 | return radians * (180.0 / Float.pi)
17 | }
18 |
19 | // These functions return the squared color difference because for distance
20 | // calculations it doesn't matter and saves an unnecessary computation.
21 |
22 | // From http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE76.html
23 | func CIE76SquaredColorDifference(_ lab1: simd_float3, lab2: simd_float3) -> Float {
24 | let (L1, a1, b1) = lab1.unpack()
25 | let (L2, a2, b2) = lab2.unpack()
26 |
27 | return pow(L2 - L1, 2) + pow(a2 - a1, 2) + pow(b2 - b1, 2)
28 | }
29 |
30 | private func C(_ a: Float, b: Float) -> Float {
31 | return sqrt(pow(a, 2) + pow(b, 2))
32 | }
33 |
34 | // From http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html
35 | func CIE94SquaredColorDifference(
36 | _ kL: Float = 1,
37 | kC: Float = 1,
38 | kH: Float = 1,
39 | K1: Float = 0.045,
40 | K2: Float = 0.015
41 | ) -> (_ lab1:simd_float3, _ lab2:simd_float3) -> Float {
42 |
43 | return { (lab1:simd_float3, lab2:simd_float3) -> Float in
44 |
45 | let (L1, a1, b1) = lab1.unpack()
46 | let (L2, a2, b2) = lab2.unpack()
47 | let ΔL = L1 - L2
48 |
49 | let (C1, C2) = (C(a1, b: b1), C(a2, b: b2))
50 | let ΔC = C1 - C2
51 |
52 | let ΔH = sqrt(pow(a1 - a2, 2) + pow(b1 - b2, 2) - pow(ΔC, 2))
53 |
54 | let Sl: Float = 1
55 | let Sc = 1 + K1 * C1
56 | let Sh = 1 + K2 * C1
57 |
58 | return pow(ΔL / (kL * Sl), 2) + pow(ΔC / (kC * Sc), 2) + pow(ΔH / (kH * Sh), 2)
59 | }
60 | }
61 |
62 | // From http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE2000.html
63 | func CIE2000SquaredColorDifference(
64 | _ kL: Float = 1,
65 | kC: Float = 1,
66 | kH: Float = 1
67 | ) -> (_ lab1:simd_float3, _ lab2:simd_float3) -> Float {
68 |
69 | return { (lab1:simd_float3, lab2:simd_float3) -> Float in
70 | let (L1, a1, b1) = lab1.unpack()
71 | let (L2, a2, b2) = lab2.unpack()
72 |
73 | let ΔLp = L2 - L1
74 | let Lbp = (L1 + L2) / 2
75 |
76 | let (C1, C2) = (C(a1, b: b1), C(a2, b: b2))
77 | let Cb = (C1 + C2) / 2
78 |
79 | let G = (1 - sqrt(pow(Cb, 7) / (pow(Cb, 7) + pow(25, 7)))) / 2
80 | let ap: (Float) -> Float = { a in
81 | return a * (1 + G)
82 | }
83 | let (a1p, a2p) = (ap(a1), ap(a2))
84 |
85 | let (C1p, C2p) = (C(a1p, b: b1), C(a2p, b: b2))
86 | let ΔCp = C2p - C1p
87 | let Cbp = (C1p + C2p) / 2
88 |
89 | let hp: (Float, Float) -> Float = { ap, b in
90 | if ap == 0 && b == 0 { return 0 }
91 | let θ = SIMDMathRadiansToDegrees(atan2(b, ap))
92 | return fmod(θ < 0 ? (θ + 360) : θ, 360)
93 | }
94 | let (h1p, h2p) = (hp(a1p, b1), hp(a2p, b2))
95 | let Δhabs = abs(h1p - h2p)
96 | let Δhp: Float = {
97 | if (C1p == 0 || C2p == 0) {
98 | return 0
99 | } else if Δhabs <= 180 {
100 | return h2p - h1p
101 | } else if h2p <= h1p {
102 | return h2p - h1p + 360
103 | } else {
104 | return h2p - h1p - 360
105 | }
106 | }()
107 |
108 | let ΔHp = 2 * sqrt(C1p * C2p) * sin(SIMDMathDegreesToRadians(Δhp / 2))
109 | let Hbp: Float = {
110 | if (C1p == 0 || C2p == 0) {
111 | return h1p + h2p
112 | } else if Δhabs > 180 {
113 | return (h1p + h2p + 360) / 2
114 | } else {
115 | return (h1p + h2p) / 2
116 | }
117 | }()
118 |
119 | var T = 1
120 | - 0.17 * cos(SIMDMathDegreesToRadians(Hbp - 30))
121 | + 0.24 * cos(SIMDMathDegreesToRadians(2 * Hbp))
122 |
123 | T = T
124 | + 0.32 * cos(SIMDMathDegreesToRadians(3 * Hbp + 6))
125 | - 0.20 * cos(SIMDMathDegreesToRadians(4 * Hbp - 63))
126 |
127 | let Sl = 1 + (0.015 * pow(Lbp - 50, 2)) / sqrt(20 + pow(Lbp - 50, 2))
128 | let Sc = 1 + 0.045 * Cbp
129 | let Sh = 1 + 0.015 * Cbp * T
130 |
131 | let Δθ = 30 * exp(-pow((Hbp - 275) / 25, 2))
132 | let Rc = 2 * sqrt(pow(Cbp, 7) / (pow(Cbp, 7) + pow(25, 7)))
133 | let Rt = -Rc * sin(SIMDMathDegreesToRadians(2 * Δθ))
134 |
135 | let Lterm = ΔLp / (kL * Sl)
136 | let Cterm = ΔCp / (kC * Sc)
137 | let Hterm = ΔHp / (kH * Sh)
138 | return pow(Lterm, 2) + pow(Cterm, 2) + pow(Hterm, 2) + Rt * Cterm * Hterm
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/ImageColorPicker/DominantColor/ColorSpaceConversion.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ColorSpaceConversion.swift
3 | // DominantColor
4 | //
5 | // Created by Jernej Strasner on 2/5/19.
6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 | #elseif os(OSX)
12 | import AppKit
13 | #endif
14 |
15 | import simd
16 |
17 | // MARK: - RGB
18 |
19 | func RGBToSRGB(_ rgbVector: simd_float3) -> simd_float3 {
20 | #if os(iOS)
21 | return rgbVector
22 | #elseif os(OSX)
23 | let rgbColor = NSColor(deviceRed: CGFloat(rgbVector.x), green: CGFloat(rgbVector.y), blue: CGFloat(rgbVector.z), alpha: 1.0)
24 | guard let srgbColor = rgbColor.usingColorSpace(.sRGB) else {
25 | fatalError("Could not convert color space")
26 | }
27 | return simd_float3(Float(srgbColor.redComponent), Float(srgbColor.greenComponent), Float(srgbColor.blueComponent))
28 | #endif
29 | }
30 |
31 | func SRGBToRGB(_ srgbVector: simd_float3) -> simd_float3 {
32 | #if os(iOS)
33 | return srgbVector
34 | #elseif os(OSX)
35 | let components: [CGFloat] = [CGFloat(srgbVector.x), CGFloat(srgbVector.y), CGFloat(srgbVector.z), 1.0]
36 | let srgbColor = NSColor(colorSpace: .sRGB, components: components, count: 4)
37 | guard let rgbColor = srgbColor.usingColorSpace(.deviceRGB) else {
38 | fatalError("Could not convert color space")
39 | }
40 | return simd_float3(Float(rgbColor.redComponent), Float(rgbColor.greenComponent), Float(rgbColor.blueComponent))
41 | #endif
42 | }
43 |
44 | // MARK: - SRGB
45 |
46 | func SRGBToLinearSRGB(_ srgbVector: simd_float3) -> simd_float3 {
47 | func f(_ c: Float) -> Float {
48 | if (c <= 0.04045) {
49 | return c / 12.92
50 | } else {
51 | return powf((c + 0.055) / 1.055, 2.4)
52 | }
53 | }
54 | return simd_float3(f(srgbVector.x), f(srgbVector.y), f(srgbVector.z))
55 | }
56 |
57 | func LinearSRGBToSRGB(_ lSrgbVector: simd_float3) -> simd_float3 {
58 | func f(_ c: Float) -> Float {
59 | if (c <= 0.0031308) {
60 | return c * 12.92
61 | } else {
62 | return (1.055 * powf(c, 1.0 / 2.4)) - 0.055
63 | }
64 | };
65 | return simd_float3(f(lSrgbVector.x), f(lSrgbVector.y), f(lSrgbVector.z));
66 | }
67 |
68 | // MARK: - XYZ (CIE 1931)
69 | // http://en.wikipedia.org/wiki/CIE_1931_color_space#Construction_of_the_CIE_XYZ_color_space_from_the_Wright.E2.80.93Guild_data
70 |
71 | let LinearSRGBToXYZMatrix = simd_float3x3([
72 | SIMD3(0.4124, 0.2126, 0.0193),
73 | SIMD3(0.3576, 0.7152, 0.1192),
74 | SIMD3(0.1805, 0.0722, 0.9505)
75 | ])
76 |
77 | func LinearSRGBToXYZ(_ linearSrgbVector: simd_float3) -> simd_float3 {
78 | let unscaledXYZVector = LinearSRGBToXYZMatrix * linearSrgbVector
79 | return unscaledXYZVector * 100.0
80 | }
81 |
82 | let XYZToLinearSRGBMatrix = simd_float3x3([
83 | SIMD3(3.2406, -0.9689, 0.0557),
84 | SIMD3(-1.5372, 1.8758, -0.2040),
85 | SIMD3(-0.4986, 0.0415, 1.0570)
86 | ])
87 |
88 | func XYZToLinearSRGB(_ xyzVector: simd_float3) -> simd_float3 {
89 | let scaledXYZVector = xyzVector / 100.0
90 | return XYZToLinearSRGBMatrix * scaledXYZVector
91 | }
92 |
93 |
94 | // MARK: - LAB
95 | // http://en.wikipedia.org/wiki/Lab_color_space#CIELAB-CIEXYZ_conversions
96 |
97 | func XYZToLAB(_ xyzVector: simd_float3, _ tristimulus: simd_float3) -> simd_float3 {
98 | func f(_ t: Float) -> Float {
99 | if (t > powf(6.0 / 29.0, 3.0)) {
100 | return powf(t, 1.0 / 3.0)
101 | } else {
102 | return ((1.0 / 3.0) * powf(29.0 / 6.0, 2.0) * t) + (4.0 / 29.0)
103 | }
104 | };
105 | let fx = f(xyzVector.x / tristimulus.x)
106 | let fy = f(xyzVector.y / tristimulus.y)
107 | let fz = f(xyzVector.z / tristimulus.z)
108 |
109 | let l = (116.0 * fy) - 16.0
110 | let a = 500 * (fx - fy)
111 | let b = 200 * (fy - fz)
112 |
113 | return simd_float3(l, a, b)
114 | }
115 |
116 | func LABToXYZ(_ labVector: simd_float3, _ tristimulus: simd_float3) -> simd_float3 {
117 | func f(_ t: Float) -> Float {
118 | if (t > (6.0 / 29.0)) {
119 | return powf(t, 3.0)
120 | } else {
121 | return 3.0 * powf(6.0 / 29.0, 2.0) * (t - (4.0 / 29.0))
122 | }
123 | };
124 | let c = (1.0 / 116.0) * (labVector.x + 16.0)
125 |
126 | let y = tristimulus.y * f(c)
127 | let x = tristimulus.x * f(c + ((1.0 / 500.0) * labVector.y))
128 | let z = tristimulus.z * f(c - ((1.0 / 200.0) * labVector.z))
129 |
130 | return simd_float3(x, y, z)
131 | }
132 |
133 | // MARK: - Public
134 |
135 | // From http://www.easyrgb.com/index.php?X=MATH&H=15#text15
136 | let D65Tristimulus = simd_float3(5.047, 100.0, 108.883)
137 |
138 | func IN_RGBToLAB(_ gVector: simd_float3) -> simd_float3 {
139 | let srgbVector = RGBToSRGB(gVector)
140 | let lSrgbVector = SRGBToLinearSRGB(srgbVector)
141 | let xyzVector = LinearSRGBToXYZ(lSrgbVector)
142 | let labVector = XYZToLAB(xyzVector, D65Tristimulus)
143 | return labVector
144 | }
145 |
146 | func IN_LABToRGB(_ gVector: simd_float3) -> simd_float3 {
147 | let xyzVector = LABToXYZ(gVector, D65Tristimulus)
148 | let lSrgbVector = XYZToLinearSRGB(xyzVector)
149 | let srgbVector = LinearSRGBToSRGB(lSrgbVector)
150 | let rgbVector = SRGBToRGB(srgbVector)
151 | return rgbVector
152 | }
153 |
--------------------------------------------------------------------------------
/ImageColorPicker/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // ImageColorPicker
4 | //
5 | // Created by Abdullah Kardas on 3.10.2022.
6 | //
7 |
8 | import SwiftUI
9 | import UIKit
10 | import PhotoLibraryPermission
11 | import CameraPermission
12 | import PermissionsKit
13 | import AlertToast
14 | import Photos
15 | import PhotosUI
16 |
17 |
18 |
19 | struct ContentView: View {
20 |
21 | @State private var shouldPresentImagePicker = false
22 | @State private var shouldPresentActionScheet = false
23 | @State private var shouldPresentCamera = false
24 | @State var colorArray:[UIColor] = []
25 | @State var showAlert:Bool = false
26 | @State var alertMessage:String = ""
27 | @State var showActionSheet: Bool = false
28 | @State var myImage = UIImage(named: "exampleimage")
29 |
30 | let columns:[GridItem] = [GridItem(.flexible(), spacing: nil, alignment: .center),GridItem(.flexible(), spacing: nil, alignment: .center),GridItem(.flexible(), spacing: nil, alignment: .center)]
31 | var body: some View {
32 | ZStack {
33 | Color.blue.opacity(0.3).ignoresSafeArea()
34 | VStack {
35 | Image(uiImage: myImage!)
36 | .resizable()
37 | .scaledToFit()
38 | .cornerRadius(16)
39 | .frame(maxWidth: .infinity).frame(height: 250).padding(.horizontal,8)
40 |
41 |
42 | Spacer()
43 |
44 | Text("Choose Photo").font(.headline.bold()).foregroundColor(.white).padding(.vertical,15).background {
45 | Capsule(style: .circular).fill(.blue).frame(width: 180)
46 | }.onTapWithBounce {shouldPresentActionScheet = true}.padding(.vertical,8)
47 |
48 | Spacer()
49 |
50 | ScrollView(.vertical, showsIndicators: false) {
51 | LazyVGrid(columns: columns) {
52 | ForEach(colorArray,id: \.self) { color in
53 | RoundedRectangle(cornerRadius: 8).frame(maxWidth: .infinity).frame(height: 45).padding(.horizontal,4)
54 | .foregroundColor(Color(uiColor: color))
55 | .overlay(alignment: .center) {
56 | HStack {
57 |
58 | Image(systemName: "rectangle.portrait.on.rectangle.portrait")
59 | Text(color.hexString)
60 | }.font(.caption).foregroundColor(.white)
61 | }.onTapWithBounce {
62 | UIPasteboard.general.string = color.hexString
63 | alertMessage = "Color Copied!"
64 | showAlert.toggle()
65 | }
66 |
67 | }
68 |
69 | }
70 | }
71 |
72 | Spacer()
73 |
74 | }
75 | .onAppear {
76 | var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
77 | configuration.selectionLimit = 1
78 | guard let colors = myImage?.dominantColors() else {return}
79 | for color in colors {
80 | colorArray.append(color)
81 | }
82 | }
83 | .onChange(of: myImage, perform: { uıımage in
84 |
85 | print("onchange")
86 | guard let colors = uıımage?.dominantColors() else {return}
87 | colorArray.removeAll()
88 | for color in colors {
89 | colorArray.append(color)
90 | }
91 | })
92 | .toast(isPresenting: $showAlert) {
93 | AlertToast(displayMode: .hud, type: .systemImage("rectangle.portrait.on.rectangle.portrait", .blue), title: alertMessage)
94 | }
95 | .fullScreenCover(isPresented: $shouldPresentImagePicker) {
96 | SUImagePickerView(sourceType: shouldPresentCamera ? .camera : .photoLibrary, image: $myImage, isPresented: $shouldPresentImagePicker)
97 | }
98 | .confirmationDialog("Select Image",isPresented: $shouldPresentActionScheet, titleVisibility: .visible, actions: {
99 | Button("Camera", role: .none) {
100 | if Permission.camera.status == .authorized {
101 | shouldPresentImagePicker = true
102 | shouldPresentCamera = true
103 | }else if Permission.camera.status == .denied{
104 | alertMessage = "App needs to access your Camera"
105 | showAlert.toggle()
106 | }else if Permission.camera.status == .notDetermined{
107 | Permission.camera.request {
108 | let result = Permission.camera.status
109 | if result == .authorized {
110 | shouldPresentImagePicker = true
111 | shouldPresentCamera = true
112 | }
113 | }
114 | }
115 |
116 | }
117 |
118 | Button("Photos", role: .none) {
119 |
120 | if Permission.photoLibrary.status == .authorized {
121 | shouldPresentImagePicker = true
122 | shouldPresentCamera = false
123 | }else if Permission.photoLibrary.status == .denied{
124 | alertMessage = "App needs to access your Galery"
125 | showAlert.toggle()
126 | }else if Permission.photoLibrary.status == .notDetermined{
127 | Permission.photoLibrary.request {
128 | let result = Permission.photoLibrary.status
129 | if result == .authorized {
130 | shouldPresentImagePicker = true
131 | shouldPresentCamera = false
132 | }
133 | }
134 | }
135 |
136 | }
137 |
138 | },message: {
139 | Text("Select your image from photos or take a new photo from camera")
140 | })
141 | }
142 | }
143 | }
144 |
145 | struct ContentView_Previews: PreviewProvider {
146 | static var previews: some View {
147 | ContentView()
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/ImageColorPicker/DominantColor/DominantColors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DominantColors.swift
3 | // DominantColor
4 | //
5 | // Created by Indragie on 12/20/14.
6 | // Copyright (c) 2014 Indragie Karunaratne. All rights reserved.
7 | //
8 |
9 | #if os(OSX)
10 | import Foundation
11 | #elseif os(iOS)
12 | import UIKit
13 | #endif
14 |
15 | import simd
16 |
17 | // MARK: Bitmaps
18 |
19 | private struct RGBAPixel {
20 | let r: UInt8
21 | let g: UInt8
22 | let b: UInt8
23 | let a: UInt8
24 | }
25 |
26 | extension RGBAPixel: Hashable {
27 | func hash(into hasher: inout Hasher) {
28 | hasher.combine(r)
29 | hasher.combine(g)
30 | hasher.combine(b)
31 | }
32 | }
33 |
34 | private func ==(lhs: RGBAPixel, rhs: RGBAPixel) -> Bool {
35 | return lhs.r == rhs.r && lhs.g == rhs.g && lhs.b == rhs.b
36 | }
37 |
38 | private func createRGBAContext(_ width: Int, height: Int) -> CGContext {
39 | return CGContext(
40 | data: nil,
41 | width: width,
42 | height: height,
43 | bitsPerComponent: 8, // bits per component
44 | bytesPerRow: width * 4, // bytes per row
45 | space: CGColorSpaceCreateDeviceRGB(),
46 | bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue).rawValue
47 | )!
48 | }
49 |
50 | // Enumerates over all of the pixels in an RGBA bitmap context
51 | // in the order that they are stored in memory, for faster access.
52 | //
53 | // From: https://www.mikeash.com/pyblog/friday-qa-2012-08-31-obtaining-and-interpreting-image-data.html
54 | private func enumerateRGBAContext(_ context: CGContext, handler: (Int, Int, RGBAPixel) -> Void) {
55 | let (width, height) = (context.width, context.height)
56 | let data = unsafeBitCast(context.data, to: UnsafeMutablePointer.self)
57 | for y in 0.. CGColor {
67 | return CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [CGFloat(rgbVector.x), CGFloat(rgbVector.y), CGFloat(rgbVector.z), 1.0])!
68 | }
69 |
70 | private extension RGBAPixel {
71 | func toRGBVector() -> simd_float3 {
72 | return simd_float3(
73 | Float(r) / Float(UInt8.max),
74 | Float(g) / Float(UInt8.max),
75 | Float(b) / Float(UInt8.max)
76 | )
77 | }
78 | }
79 |
80 | // MARK: Clustering
81 |
82 | extension simd_float3 : ClusteredType {}
83 |
84 | // MARK: Main
85 |
86 | public enum GroupingAccuracy {
87 | case low // CIE 76 - Euclidian distance
88 | case medium // CIE 94 - Perceptual non-uniformity corrections
89 | case high // CIE 2000 - Additional corrections for neutral colors, lightness, chroma, and hue
90 | }
91 |
92 | public struct DefaultParameterValues {
93 | public static var maxSampledPixels: Int = 1000
94 | public static var accuracy: GroupingAccuracy = .medium
95 | public static var seed: UInt64 = 3571
96 | public static var memoizeConversions: Bool = false
97 | }
98 |
99 | /**
100 | Computes the dominant colors in an image
101 |
102 | - parameter image: The image
103 | - parameter maxSampledPixels: Maximum number of pixels to sample in the image. If
104 | the total number of pixels in the image exceeds this
105 | value, it will be downsampled to meet the constraint.
106 | - parameter accuracy: Level of accuracy to use when grouping similar colors.
107 | Higher accuracy will come with a performance tradeoff.
108 | - parameter seed: Seed to use when choosing the initial points for grouping
109 | of similar colors. The same seed is guaranteed to return
110 | the same colors every time.
111 | - parameter memoizeConversions: Whether to memoize conversions from RGB to the LAB color
112 | space (used for grouping similar colors). Memoization
113 | will only yield better performance for large values of
114 | `maxSampledPixels` in images that are primarily comprised
115 | of flat colors. If this information about the image is
116 | not known beforehand, it is best to not memoize.
117 |
118 | - returns: A list of dominant colors in the image sorted from most dominant to
119 | least dominant.
120 | */
121 | public func dominantColorsInImage(
122 | _ image: CGImage,
123 | maxSampledPixels: Int = DefaultParameterValues.maxSampledPixels,
124 | accuracy: GroupingAccuracy = DefaultParameterValues.accuracy,
125 | seed: UInt64 = DefaultParameterValues.seed,
126 | memoizeConversions: Bool = DefaultParameterValues.memoizeConversions
127 | ) -> [CGColor] {
128 |
129 | let (width, height) = (image.width, image.height)
130 | let (scaledWidth, scaledHeight) = scaledDimensionsForPixelLimit(maxSampledPixels, width: width, height: height)
131 |
132 | // Downsample the image if necessary, so that the total number of
133 | // pixels sampled does not exceed the specified maximum.
134 | let context = createRGBAContext(scaledWidth, height: scaledHeight)
135 | context.draw(image, in: CGRect(x: 0, y: 0, width: Int(scaledWidth), height: Int(scaledHeight)))
136 |
137 | // Get the RGB colors from the bitmap context, ignoring any pixels
138 | // that have alpha transparency.
139 | // Also convert the colors to the LAB color space
140 | var labValues = [simd_float3]()
141 | labValues.reserveCapacity(Int(scaledWidth * scaledHeight))
142 |
143 | let RGBToLAB: (RGBAPixel) -> simd_float3 = {
144 | let f: (RGBAPixel) -> simd_float3 = { IN_RGBToLAB($0.toRGBVector()) }
145 | return memoizeConversions ? memoize(f) : f
146 | }()
147 | enumerateRGBAContext(context) { (_, _, pixel) in
148 | if pixel.a == UInt8.max {
149 | labValues.append(RGBToLAB(pixel))
150 | }
151 | }
152 | // Cluster the colors using the k-means algorithm
153 | let k = selectKForElements(labValues)
154 | var clusters = kmeans(labValues, k: k, seed: seed, distance: distanceForAccuracy(accuracy))
155 |
156 | // Sort the clusters by size in descending order so that the
157 | // most dominant colors come first.
158 | clusters.sort { $0.size > $1.size }
159 |
160 | return clusters.map { RGBVectorToCGColor(IN_LABToRGB($0.centroid)) }
161 | }
162 |
163 | private func distanceForAccuracy(_ accuracy: GroupingAccuracy) -> (simd_float3, simd_float3) -> Float {
164 | switch accuracy {
165 | case .low:
166 | return CIE76SquaredColorDifference
167 | case .medium:
168 | return CIE94SquaredColorDifference()
169 | case .high:
170 | return CIE2000SquaredColorDifference()
171 | }
172 | }
173 |
174 | // Computes the proportionally scaled dimensions such that the
175 | // total number of pixels does not exceed the specified limit.
176 | private func scaledDimensionsForPixelLimit(_ limit: Int, width: Int, height: Int) -> (Int, Int) {
177 | if (width * height > limit) {
178 | let ratio = Float(width) / Float(height)
179 | let maxWidth = sqrtf(ratio * Float(limit))
180 | return (Int(maxWidth), Int(Float(limit) / maxWidth))
181 | }
182 | return (width, height)
183 | }
184 |
185 | private func selectKForElements(_ elements: [T]) -> Int {
186 | // Seems like a magic number...
187 | return 16
188 | }
189 |
--------------------------------------------------------------------------------
/ImageColorPicker.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 56;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | BE2A6C9C28EB20720023856A /* ImageColorPickerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE2A6C9B28EB20720023856A /* ImageColorPickerApp.swift */; };
11 | BE2A6C9E28EB20720023856A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE2A6C9D28EB20720023856A /* ContentView.swift */; };
12 | BE2A6CA028EB20760023856A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BE2A6C9F28EB20760023856A /* Assets.xcassets */; };
13 | BE2A6CA328EB20760023856A /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BE2A6CA228EB20760023856A /* Preview Assets.xcassets */; };
14 | BE2A6CAB28EB21600023856A /* CameraPermission in Frameworks */ = {isa = PBXBuildFile; productRef = BE2A6CAA28EB21600023856A /* CameraPermission */; };
15 | BE2A6CAD28EB21600023856A /* PhotoLibraryPermission in Frameworks */ = {isa = PBXBuildFile; productRef = BE2A6CAC28EB21600023856A /* PhotoLibraryPermission */; };
16 | BE2A6CB928EB26CE0023856A /* DominantColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE2A6CB228EB26CD0023856A /* DominantColors.swift */; };
17 | BE2A6CBA28EB26CE0023856A /* INVector3SwiftExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE2A6CB328EB26CD0023856A /* INVector3SwiftExtensions.swift */; };
18 | BE2A6CBB28EB26CE0023856A /* PlatformExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE2A6CB428EB26CD0023856A /* PlatformExtensions.swift */; };
19 | BE2A6CBC28EB26CE0023856A /* ColorDifference.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE2A6CB528EB26CE0023856A /* ColorDifference.swift */; };
20 | BE2A6CBD28EB26CE0023856A /* ColorSpaceConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE2A6CB628EB26CE0023856A /* ColorSpaceConversion.swift */; };
21 | BE2A6CBE28EB26CE0023856A /* KMeans.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE2A6CB728EB26CE0023856A /* KMeans.swift */; };
22 | BE2A6CBF28EB26CE0023856A /* Memoization.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE2A6CB828EB26CE0023856A /* Memoization.swift */; };
23 | BE2A6CC128EB34A30023856A /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE2A6CC028EB34A30023856A /* Extensions.swift */; };
24 | BE2A6CC328EB35B50023856A /* Modifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE2A6CC228EB35B50023856A /* Modifiers.swift */; };
25 | BE2A6CC628EB3D9A0023856A /* AlertToast in Frameworks */ = {isa = PBXBuildFile; productRef = BE2A6CC528EB3D9A0023856A /* AlertToast */; };
26 | BEBD233528F155BF00626F89 /* SUImagePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEBD233428F155BF00626F89 /* SUImagePickerView.swift */; };
27 | /* End PBXBuildFile section */
28 |
29 | /* Begin PBXFileReference section */
30 | BE2A6C9828EB20720023856A /* ImageColorPicker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ImageColorPicker.app; sourceTree = BUILT_PRODUCTS_DIR; };
31 | BE2A6C9B28EB20720023856A /* ImageColorPickerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageColorPickerApp.swift; sourceTree = ""; };
32 | BE2A6C9D28EB20720023856A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
33 | BE2A6C9F28EB20760023856A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
34 | BE2A6CA228EB20760023856A /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
35 | BE2A6CAE28EB21760023856A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; };
36 | BE2A6CB228EB26CD0023856A /* DominantColors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DominantColors.swift; sourceTree = ""; };
37 | BE2A6CB328EB26CD0023856A /* INVector3SwiftExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = INVector3SwiftExtensions.swift; sourceTree = ""; };
38 | BE2A6CB428EB26CD0023856A /* PlatformExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlatformExtensions.swift; sourceTree = ""; };
39 | BE2A6CB528EB26CE0023856A /* ColorDifference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorDifference.swift; sourceTree = ""; };
40 | BE2A6CB628EB26CE0023856A /* ColorSpaceConversion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorSpaceConversion.swift; sourceTree = ""; };
41 | BE2A6CB728EB26CE0023856A /* KMeans.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KMeans.swift; sourceTree = ""; };
42 | BE2A6CB828EB26CE0023856A /* Memoization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Memoization.swift; sourceTree = ""; };
43 | BE2A6CC028EB34A30023856A /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; };
44 | BE2A6CC228EB35B50023856A /* Modifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modifiers.swift; sourceTree = ""; };
45 | BEBD233428F155BF00626F89 /* SUImagePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SUImagePickerView.swift; sourceTree = ""; };
46 | /* End PBXFileReference section */
47 |
48 | /* Begin PBXFrameworksBuildPhase section */
49 | BE2A6C9528EB20720023856A /* Frameworks */ = {
50 | isa = PBXFrameworksBuildPhase;
51 | buildActionMask = 2147483647;
52 | files = (
53 | BE2A6CC628EB3D9A0023856A /* AlertToast in Frameworks */,
54 | BE2A6CAD28EB21600023856A /* PhotoLibraryPermission in Frameworks */,
55 | BE2A6CAB28EB21600023856A /* CameraPermission in Frameworks */,
56 | );
57 | runOnlyForDeploymentPostprocessing = 0;
58 | };
59 | /* End PBXFrameworksBuildPhase section */
60 |
61 | /* Begin PBXGroup section */
62 | BE2A6C8F28EB20720023856A = {
63 | isa = PBXGroup;
64 | children = (
65 | BE2A6C9A28EB20720023856A /* ImageColorPicker */,
66 | BE2A6C9928EB20720023856A /* Products */,
67 | );
68 | sourceTree = "";
69 | };
70 | BE2A6C9928EB20720023856A /* Products */ = {
71 | isa = PBXGroup;
72 | children = (
73 | BE2A6C9828EB20720023856A /* ImageColorPicker.app */,
74 | );
75 | name = Products;
76 | sourceTree = "";
77 | };
78 | BE2A6C9A28EB20720023856A /* ImageColorPicker */ = {
79 | isa = PBXGroup;
80 | children = (
81 | BEBD233628F155CE00626F89 /* ImagePickerLibrary */,
82 | BE2A6CB128EB26B80023856A /* DominantColor */,
83 | BE2A6CAE28EB21760023856A /* Info.plist */,
84 | BE2A6C9B28EB20720023856A /* ImageColorPickerApp.swift */,
85 | BE2A6C9D28EB20720023856A /* ContentView.swift */,
86 | BE2A6C9F28EB20760023856A /* Assets.xcassets */,
87 | BE2A6CA128EB20760023856A /* Preview Content */,
88 | BE2A6CC028EB34A30023856A /* Extensions.swift */,
89 | BE2A6CC228EB35B50023856A /* Modifiers.swift */,
90 | );
91 | path = ImageColorPicker;
92 | sourceTree = "";
93 | };
94 | BE2A6CA128EB20760023856A /* Preview Content */ = {
95 | isa = PBXGroup;
96 | children = (
97 | BE2A6CA228EB20760023856A /* Preview Assets.xcassets */,
98 | );
99 | path = "Preview Content";
100 | sourceTree = "";
101 | };
102 | BE2A6CB128EB26B80023856A /* DominantColor */ = {
103 | isa = PBXGroup;
104 | children = (
105 | BE2A6CB528EB26CE0023856A /* ColorDifference.swift */,
106 | BE2A6CB628EB26CE0023856A /* ColorSpaceConversion.swift */,
107 | BE2A6CB228EB26CD0023856A /* DominantColors.swift */,
108 | BE2A6CB328EB26CD0023856A /* INVector3SwiftExtensions.swift */,
109 | BE2A6CB728EB26CE0023856A /* KMeans.swift */,
110 | BE2A6CB828EB26CE0023856A /* Memoization.swift */,
111 | BE2A6CB428EB26CD0023856A /* PlatformExtensions.swift */,
112 | );
113 | path = DominantColor;
114 | sourceTree = "";
115 | };
116 | BEBD233628F155CE00626F89 /* ImagePickerLibrary */ = {
117 | isa = PBXGroup;
118 | children = (
119 | BEBD233428F155BF00626F89 /* SUImagePickerView.swift */,
120 | );
121 | path = ImagePickerLibrary;
122 | sourceTree = "";
123 | };
124 | /* End PBXGroup section */
125 |
126 | /* Begin PBXNativeTarget section */
127 | BE2A6C9728EB20720023856A /* ImageColorPicker */ = {
128 | isa = PBXNativeTarget;
129 | buildConfigurationList = BE2A6CA628EB20760023856A /* Build configuration list for PBXNativeTarget "ImageColorPicker" */;
130 | buildPhases = (
131 | BE2A6C9428EB20720023856A /* Sources */,
132 | BE2A6C9528EB20720023856A /* Frameworks */,
133 | BE2A6C9628EB20720023856A /* Resources */,
134 | );
135 | buildRules = (
136 | );
137 | dependencies = (
138 | );
139 | name = ImageColorPicker;
140 | packageProductDependencies = (
141 | BE2A6CAA28EB21600023856A /* CameraPermission */,
142 | BE2A6CAC28EB21600023856A /* PhotoLibraryPermission */,
143 | BE2A6CC528EB3D9A0023856A /* AlertToast */,
144 | );
145 | productName = ImageColorPicker;
146 | productReference = BE2A6C9828EB20720023856A /* ImageColorPicker.app */;
147 | productType = "com.apple.product-type.application";
148 | };
149 | /* End PBXNativeTarget section */
150 |
151 | /* Begin PBXProject section */
152 | BE2A6C9028EB20720023856A /* Project object */ = {
153 | isa = PBXProject;
154 | attributes = {
155 | BuildIndependentTargetsInParallel = 1;
156 | LastSwiftUpdateCheck = 1400;
157 | LastUpgradeCheck = 1400;
158 | TargetAttributes = {
159 | BE2A6C9728EB20720023856A = {
160 | CreatedOnToolsVersion = 14.0;
161 | };
162 | };
163 | };
164 | buildConfigurationList = BE2A6C9328EB20720023856A /* Build configuration list for PBXProject "ImageColorPicker" */;
165 | compatibilityVersion = "Xcode 14.0";
166 | developmentRegion = en;
167 | hasScannedForEncodings = 0;
168 | knownRegions = (
169 | en,
170 | Base,
171 | );
172 | mainGroup = BE2A6C8F28EB20720023856A;
173 | packageReferences = (
174 | BE2A6CA928EB21600023856A /* XCRemoteSwiftPackageReference "PermissionsKit" */,
175 | BE2A6CC428EB3D9A0023856A /* XCRemoteSwiftPackageReference "AlertToast" */,
176 | );
177 | productRefGroup = BE2A6C9928EB20720023856A /* Products */;
178 | projectDirPath = "";
179 | projectRoot = "";
180 | targets = (
181 | BE2A6C9728EB20720023856A /* ImageColorPicker */,
182 | );
183 | };
184 | /* End PBXProject section */
185 |
186 | /* Begin PBXResourcesBuildPhase section */
187 | BE2A6C9628EB20720023856A /* Resources */ = {
188 | isa = PBXResourcesBuildPhase;
189 | buildActionMask = 2147483647;
190 | files = (
191 | BE2A6CA328EB20760023856A /* Preview Assets.xcassets in Resources */,
192 | BE2A6CA028EB20760023856A /* Assets.xcassets in Resources */,
193 | );
194 | runOnlyForDeploymentPostprocessing = 0;
195 | };
196 | /* End PBXResourcesBuildPhase section */
197 |
198 | /* Begin PBXSourcesBuildPhase section */
199 | BE2A6C9428EB20720023856A /* Sources */ = {
200 | isa = PBXSourcesBuildPhase;
201 | buildActionMask = 2147483647;
202 | files = (
203 | BE2A6C9E28EB20720023856A /* ContentView.swift in Sources */,
204 | BE2A6CC328EB35B50023856A /* Modifiers.swift in Sources */,
205 | BE2A6CBB28EB26CE0023856A /* PlatformExtensions.swift in Sources */,
206 | BE2A6CBD28EB26CE0023856A /* ColorSpaceConversion.swift in Sources */,
207 | BEBD233528F155BF00626F89 /* SUImagePickerView.swift in Sources */,
208 | BE2A6CBC28EB26CE0023856A /* ColorDifference.swift in Sources */,
209 | BE2A6C9C28EB20720023856A /* ImageColorPickerApp.swift in Sources */,
210 | BE2A6CC128EB34A30023856A /* Extensions.swift in Sources */,
211 | BE2A6CB928EB26CE0023856A /* DominantColors.swift in Sources */,
212 | BE2A6CBF28EB26CE0023856A /* Memoization.swift in Sources */,
213 | BE2A6CBE28EB26CE0023856A /* KMeans.swift in Sources */,
214 | BE2A6CBA28EB26CE0023856A /* INVector3SwiftExtensions.swift in Sources */,
215 | );
216 | runOnlyForDeploymentPostprocessing = 0;
217 | };
218 | /* End PBXSourcesBuildPhase section */
219 |
220 | /* Begin XCBuildConfiguration section */
221 | BE2A6CA428EB20760023856A /* Debug */ = {
222 | isa = XCBuildConfiguration;
223 | buildSettings = {
224 | ALWAYS_SEARCH_USER_PATHS = NO;
225 | CLANG_ANALYZER_NONNULL = YES;
226 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
227 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
228 | CLANG_ENABLE_MODULES = YES;
229 | CLANG_ENABLE_OBJC_ARC = YES;
230 | CLANG_ENABLE_OBJC_WEAK = YES;
231 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
232 | CLANG_WARN_BOOL_CONVERSION = YES;
233 | CLANG_WARN_COMMA = YES;
234 | CLANG_WARN_CONSTANT_CONVERSION = YES;
235 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
236 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
237 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
238 | CLANG_WARN_EMPTY_BODY = YES;
239 | CLANG_WARN_ENUM_CONVERSION = YES;
240 | CLANG_WARN_INFINITE_RECURSION = YES;
241 | CLANG_WARN_INT_CONVERSION = YES;
242 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
243 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
244 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
245 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
246 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
247 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
248 | CLANG_WARN_STRICT_PROTOTYPES = YES;
249 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
250 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
251 | CLANG_WARN_UNREACHABLE_CODE = YES;
252 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
253 | COPY_PHASE_STRIP = NO;
254 | DEBUG_INFORMATION_FORMAT = dwarf;
255 | ENABLE_STRICT_OBJC_MSGSEND = YES;
256 | ENABLE_TESTABILITY = YES;
257 | GCC_C_LANGUAGE_STANDARD = gnu11;
258 | GCC_DYNAMIC_NO_PIC = NO;
259 | GCC_NO_COMMON_BLOCKS = YES;
260 | GCC_OPTIMIZATION_LEVEL = 0;
261 | GCC_PREPROCESSOR_DEFINITIONS = (
262 | "DEBUG=1",
263 | "$(inherited)",
264 | );
265 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
266 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
267 | GCC_WARN_UNDECLARED_SELECTOR = YES;
268 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
269 | GCC_WARN_UNUSED_FUNCTION = YES;
270 | GCC_WARN_UNUSED_VARIABLE = YES;
271 | IPHONEOS_DEPLOYMENT_TARGET = 16.0;
272 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
273 | MTL_FAST_MATH = YES;
274 | ONLY_ACTIVE_ARCH = YES;
275 | SDKROOT = iphoneos;
276 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
277 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
278 | };
279 | name = Debug;
280 | };
281 | BE2A6CA528EB20760023856A /* Release */ = {
282 | isa = XCBuildConfiguration;
283 | buildSettings = {
284 | ALWAYS_SEARCH_USER_PATHS = NO;
285 | CLANG_ANALYZER_NONNULL = YES;
286 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
287 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
288 | CLANG_ENABLE_MODULES = YES;
289 | CLANG_ENABLE_OBJC_ARC = YES;
290 | CLANG_ENABLE_OBJC_WEAK = YES;
291 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
292 | CLANG_WARN_BOOL_CONVERSION = YES;
293 | CLANG_WARN_COMMA = YES;
294 | CLANG_WARN_CONSTANT_CONVERSION = YES;
295 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
296 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
297 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
298 | CLANG_WARN_EMPTY_BODY = YES;
299 | CLANG_WARN_ENUM_CONVERSION = YES;
300 | CLANG_WARN_INFINITE_RECURSION = YES;
301 | CLANG_WARN_INT_CONVERSION = YES;
302 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
303 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
304 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
305 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
306 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
307 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
308 | CLANG_WARN_STRICT_PROTOTYPES = YES;
309 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
310 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
311 | CLANG_WARN_UNREACHABLE_CODE = YES;
312 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
313 | COPY_PHASE_STRIP = NO;
314 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
315 | ENABLE_NS_ASSERTIONS = NO;
316 | ENABLE_STRICT_OBJC_MSGSEND = YES;
317 | GCC_C_LANGUAGE_STANDARD = gnu11;
318 | GCC_NO_COMMON_BLOCKS = YES;
319 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
320 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
321 | GCC_WARN_UNDECLARED_SELECTOR = YES;
322 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
323 | GCC_WARN_UNUSED_FUNCTION = YES;
324 | GCC_WARN_UNUSED_VARIABLE = YES;
325 | IPHONEOS_DEPLOYMENT_TARGET = 16.0;
326 | MTL_ENABLE_DEBUG_INFO = NO;
327 | MTL_FAST_MATH = YES;
328 | SDKROOT = iphoneos;
329 | SWIFT_COMPILATION_MODE = wholemodule;
330 | SWIFT_OPTIMIZATION_LEVEL = "-O";
331 | VALIDATE_PRODUCT = YES;
332 | };
333 | name = Release;
334 | };
335 | BE2A6CA728EB20760023856A /* Debug */ = {
336 | isa = XCBuildConfiguration;
337 | buildSettings = {
338 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
339 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
340 | CODE_SIGN_STYLE = Automatic;
341 | CURRENT_PROJECT_VERSION = 1;
342 | DEVELOPMENT_ASSET_PATHS = "\"ImageColorPicker/Preview Content\"";
343 | DEVELOPMENT_TEAM = MHT8X3GHR9;
344 | ENABLE_PREVIEWS = YES;
345 | GENERATE_INFOPLIST_FILE = YES;
346 | INFOPLIST_FILE = ImageColorPicker/Info.plist;
347 | INFOPLIST_KEY_NSCameraUsageDescription = "Needs Camera Permission";
348 | INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Needs photo permission";
349 | INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Photo Permission";
350 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
351 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
352 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
353 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
354 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
355 | LD_RUNPATH_SEARCH_PATHS = (
356 | "$(inherited)",
357 | "@executable_path/Frameworks",
358 | );
359 | MARKETING_VERSION = 1.0;
360 | PRODUCT_BUNDLE_IDENTIFIER = akardas16.ImageColorPicker;
361 | PRODUCT_NAME = "$(TARGET_NAME)";
362 | SWIFT_EMIT_LOC_STRINGS = YES;
363 | SWIFT_VERSION = 5.0;
364 | TARGETED_DEVICE_FAMILY = "1,2";
365 | };
366 | name = Debug;
367 | };
368 | BE2A6CA828EB20760023856A /* Release */ = {
369 | isa = XCBuildConfiguration;
370 | buildSettings = {
371 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
372 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
373 | CODE_SIGN_STYLE = Automatic;
374 | CURRENT_PROJECT_VERSION = 1;
375 | DEVELOPMENT_ASSET_PATHS = "\"ImageColorPicker/Preview Content\"";
376 | DEVELOPMENT_TEAM = MHT8X3GHR9;
377 | ENABLE_PREVIEWS = YES;
378 | GENERATE_INFOPLIST_FILE = YES;
379 | INFOPLIST_FILE = ImageColorPicker/Info.plist;
380 | INFOPLIST_KEY_NSCameraUsageDescription = "Needs Camera Permission";
381 | INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Needs photo permission";
382 | INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Photo Permission";
383 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
384 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
385 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
386 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
387 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
388 | LD_RUNPATH_SEARCH_PATHS = (
389 | "$(inherited)",
390 | "@executable_path/Frameworks",
391 | );
392 | MARKETING_VERSION = 1.0;
393 | PRODUCT_BUNDLE_IDENTIFIER = akardas16.ImageColorPicker;
394 | PRODUCT_NAME = "$(TARGET_NAME)";
395 | SWIFT_EMIT_LOC_STRINGS = YES;
396 | SWIFT_VERSION = 5.0;
397 | TARGETED_DEVICE_FAMILY = "1,2";
398 | };
399 | name = Release;
400 | };
401 | /* End XCBuildConfiguration section */
402 |
403 | /* Begin XCConfigurationList section */
404 | BE2A6C9328EB20720023856A /* Build configuration list for PBXProject "ImageColorPicker" */ = {
405 | isa = XCConfigurationList;
406 | buildConfigurations = (
407 | BE2A6CA428EB20760023856A /* Debug */,
408 | BE2A6CA528EB20760023856A /* Release */,
409 | );
410 | defaultConfigurationIsVisible = 0;
411 | defaultConfigurationName = Release;
412 | };
413 | BE2A6CA628EB20760023856A /* Build configuration list for PBXNativeTarget "ImageColorPicker" */ = {
414 | isa = XCConfigurationList;
415 | buildConfigurations = (
416 | BE2A6CA728EB20760023856A /* Debug */,
417 | BE2A6CA828EB20760023856A /* Release */,
418 | );
419 | defaultConfigurationIsVisible = 0;
420 | defaultConfigurationName = Release;
421 | };
422 | /* End XCConfigurationList section */
423 |
424 | /* Begin XCRemoteSwiftPackageReference section */
425 | BE2A6CA928EB21600023856A /* XCRemoteSwiftPackageReference "PermissionsKit" */ = {
426 | isa = XCRemoteSwiftPackageReference;
427 | repositoryURL = "https://github.com/sparrowcode/PermissionsKit";
428 | requirement = {
429 | branch = main;
430 | kind = branch;
431 | };
432 | };
433 | BE2A6CC428EB3D9A0023856A /* XCRemoteSwiftPackageReference "AlertToast" */ = {
434 | isa = XCRemoteSwiftPackageReference;
435 | repositoryURL = "https://github.com/elai950/AlertToast.git";
436 | requirement = {
437 | branch = master;
438 | kind = branch;
439 | };
440 | };
441 | /* End XCRemoteSwiftPackageReference section */
442 |
443 | /* Begin XCSwiftPackageProductDependency section */
444 | BE2A6CAA28EB21600023856A /* CameraPermission */ = {
445 | isa = XCSwiftPackageProductDependency;
446 | package = BE2A6CA928EB21600023856A /* XCRemoteSwiftPackageReference "PermissionsKit" */;
447 | productName = CameraPermission;
448 | };
449 | BE2A6CAC28EB21600023856A /* PhotoLibraryPermission */ = {
450 | isa = XCSwiftPackageProductDependency;
451 | package = BE2A6CA928EB21600023856A /* XCRemoteSwiftPackageReference "PermissionsKit" */;
452 | productName = PhotoLibraryPermission;
453 | };
454 | BE2A6CC528EB3D9A0023856A /* AlertToast */ = {
455 | isa = XCSwiftPackageProductDependency;
456 | package = BE2A6CC428EB3D9A0023856A /* XCRemoteSwiftPackageReference "AlertToast" */;
457 | productName = AlertToast;
458 | };
459 | /* End XCSwiftPackageProductDependency section */
460 | };
461 | rootObject = BE2A6C9028EB20720023856A /* Project object */;
462 | }
463 |
--------------------------------------------------------------------------------