├── ImageColorPicker.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── xcuserdata │ └── akardas16.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── ImageColorPicker ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── exampleimage.imageset │ │ ├── Contents.json │ │ └── exampleimage.jpg ├── ContentView.swift ├── DominantColor │ ├── ColorDifference.swift │ ├── ColorSpaceConversion.swift │ ├── DominantColors.swift │ ├── INVector3SwiftExtensions.swift │ ├── KMeans.swift │ ├── Memoization.swift │ └── PlatformExtensions.swift ├── Extensions.swift ├── ImageColorPickerApp.swift ├── ImagePickerLibrary │ └── SUImagePickerView.swift ├── Info.plist ├── Modifiers.swift └── Preview Content │ └── Preview Assets.xcassets │ └── Contents.json └── README.md /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 | -------------------------------------------------------------------------------- /ImageColorPicker.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ImageColorPicker.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ImageColorPicker.xcodeproj/xcuserdata/akardas16.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ImageColorPicker.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ImageColorPicker/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ImageColorPicker/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ImageColorPicker/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ImageColorPicker/Assets.xcassets/exampleimage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "exampleimage.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ImageColorPicker/Assets.xcassets/exampleimage.imageset/exampleimage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akardas16/ImageColorPicker/20e068afe9885ca617b09ce41ee40e642b71f069/ImageColorPicker/Assets.xcassets/exampleimage.imageset/exampleimage.jpg -------------------------------------------------------------------------------- /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/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/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/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/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..(_ 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/DominantColor/PlatformExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlatformExtensions.swift 3 | // DominantColor 4 | // 5 | // Created by Indragie on 12/25/14. 6 | // Copyright (c) 2014 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | #if os(OSX) 10 | import Cocoa 11 | 12 | public extension NSImage { 13 | /** 14 | Computes the dominant colors in the receiver 15 | 16 | - parameter maxSampledPixels: Maximum number of pixels to sample in the image. If 17 | the total number of pixels in the image exceeds this 18 | value, it will be downsampled to meet the constraint. 19 | - parameter accuracy: Level of accuracy to use when grouping similar colors. 20 | Higher accuracy will come with a performance tradeoff. 21 | - parameter seed: Seed to use when choosing the initial points for grouping 22 | of similar colors. The same seed is guaranteed to return 23 | the same colors every time. 24 | - parameter memoizeConversions: Whether to memoize conversions from RGB to the LAB color 25 | space (used for grouping similar colors). Memoization 26 | will only yield better performance for large values of 27 | `maxSampledPixels` in images that are primarily comprised 28 | of flat colors. If this information about the image is 29 | not known beforehand, it is best to not memoize. 30 | 31 | - returns: A list of dominant colors in the image sorted from most dominant to 32 | least dominant. 33 | */ 34 | func dominantColors( 35 | maxSampledPixels: Int = DefaultParameterValues.maxSampledPixels, 36 | accuracy: GroupingAccuracy = DefaultParameterValues.accuracy, 37 | seed: UInt64 = DefaultParameterValues.seed, 38 | memoizeConversions: Bool = DefaultParameterValues.memoizeConversions 39 | ) -> [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/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/ImageColorPickerApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageColorPickerApp.swift 3 | // ImageColorPicker 4 | // 5 | // Created by Abdullah Kardas on 3.10.2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct ImageColorPickerApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /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/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImageColorPicker 2 | This project shows how grab colors from an image 3 | 4 | 5 |

6 | Light 7 |         8 | Dark 9 |         10 | Dark 11 |         12 | Dark 13 |

14 | 15 | --------------------------------------------------------------------------------