├── .github └── workflows │ └── test.yml ├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── Assets ├── color_difference_deltaE_CIE94.jpg ├── color_difference_deltaE_RGB.jpg ├── color_palette_albums.jpg ├── colorkit_banner.jpg └── dominant_colors.jpg ├── ColorKit ├── ColorKit.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ ├── xcbaselines │ │ └── A181C8602404728F0008D4B7.xcbaseline │ │ │ ├── FD9A0D44-3456-44B1-ADA8-6380851AC44B.plist │ │ │ └── Info.plist │ │ └── xcschemes │ │ ├── ColorKit.xcscheme │ │ └── ColorKitSampleApp.xcscheme ├── ColorKit │ ├── AverageColor.swift │ ├── CMYK.swift │ ├── ColorKit.h │ ├── ColorPalette.swift │ ├── ComplementaryColor.swift │ ├── ContrastRatio.swift │ ├── Difference.swift │ ├── DominantColors.swift │ ├── Extensions │ │ ├── CGFloatExtensions.swift │ │ ├── CGSizeExtensions.swift │ │ └── UIImageExtensions.swift │ ├── Hex.swift │ ├── Info.plist │ ├── Lab.swift │ ├── Localization │ │ ├── en.lproj │ │ │ └── Localizable.strings │ │ └── fr.lproj │ │ │ └── Localizable.strings │ ├── Name.swift │ ├── RGB.swift │ ├── Random.swift │ ├── RelativeLuminance.swift │ └── XYZ.swift ├── ColorKitSampleApp │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── Album_cover_1.imageset │ │ │ ├── 61IWOsLc2QL._SS500_.jpg │ │ │ └── Contents.json │ │ ├── Album_cover_2.imageset │ │ │ ├── Contents.json │ │ │ └── cri_000000319870.jpg │ │ ├── Album_cover_3.imageset │ │ │ ├── Contents.json │ │ │ └── Hotel-California.jpg │ │ ├── Album_cover_4.imageset │ │ │ ├── Contents.json │ │ │ └── Never_Mind_the_Bollocks,_Here's_the_Sex_Pistols.png │ │ ├── Album_cover_5.imageset │ │ │ ├── Contents.json │ │ │ └── Pink-Floyd-Dark-Side-of-the-Moon-album-covers-billboard-1000x1000-compressed.jpg │ │ ├── Album_cover_6.imageset │ │ │ ├── Contents.json │ │ │ └── Tommy.jpg │ │ ├── Album_cover_7.imageset │ │ │ ├── Contents.json │ │ │ └── PetSoundsCover.jpg │ │ ├── Album_cover_8.imageset │ │ │ ├── 51KHXRnEG5L._SX466_.jpg │ │ │ └── Contents.json │ │ ├── Album_cover_9.imageset │ │ │ ├── Contents.json │ │ │ └── iconic-80s-rap-album-covers-featuring-classic-sneakers-2.jpg │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── icon_20pt@2x.jpg │ │ │ ├── icon_20pt@3x.jpg │ │ │ ├── icon_29pt@2x.jpg │ │ │ ├── icon_29pt@3x.jpg │ │ │ ├── icon_40pt@2x.jpg │ │ │ ├── icon_40pt@3x.jpg │ │ │ ├── icon_60pt@2x.jpg │ │ │ └── icon_60pt@3x.jpg │ │ ├── Banner.imageset │ │ │ ├── Banner@2x.png │ │ │ ├── Banner@3x.jpg │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── Dominant_Color_Image_1.imageset │ │ │ ├── Contents.json │ │ │ └── Dominant_Color_Image_4.jpg │ │ ├── Dominant_Color_Image_2.imageset │ │ │ ├── Contents.json │ │ │ └── Dominant_Color_Image_2.jpg │ │ ├── Dominant_Color_Image_3.imageset │ │ │ ├── Contents.json │ │ │ └── Dominant_Color_Image_3.jpg │ │ ├── Dominant_Color_Image_4.imageset │ │ │ ├── Contents.json │ │ │ └── Dominant_Color_Image_1.jpg │ │ ├── Dominant_Color_Image_5.imageset │ │ │ ├── Contents.json │ │ │ └── Dominant_Color_Image_5.jpg │ │ └── Dominant_Color_Image_6.imageset │ │ │ ├── Contents.json │ │ │ └── Dominant_Color_Image_6.jpg │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── ColorDetailViewController.swift │ ├── DominantColorsViewController.swift │ ├── ImageColorsViewController.swift │ ├── Info.plist │ ├── PaletteViewController.swift │ └── SceneDelegate.swift └── ColorKitTests │ ├── AverageColorTests.swift │ ├── CGFloatExtensionsTests.swift │ ├── CGSizeExtensionsTests.swift │ ├── CMYKTests.swift │ ├── ColorKitTests-Bridging-Header.h │ ├── ComparisonTests.swift │ ├── ComplementaryColorTests.swift │ ├── ContrastRatioTests.swift │ ├── DominantColorQualityTests.swift │ ├── DominantColorsTests.swift │ ├── HexTests.swift │ ├── Info.plist │ ├── LabTests.swift │ ├── NameTests.swift │ ├── PaletteTests.swift │ ├── RGBTests.swift │ ├── RelativeLuminanceTests.swift │ ├── Resources │ ├── Black_White_Square.jpg │ ├── Blue_Green_Square.jpg │ ├── Blue_Square_1x1.jpg │ ├── Green_Square.jpg │ ├── Purple_Square.jpg │ ├── Red_Green_Blue.png │ ├── Red_Green_Blue_Black_Mini.png │ ├── Red_Green_Blue_Random.png │ ├── Red_Green_Blue_Random_Mini.png │ ├── Test_Image_1.Jpeg │ ├── Test_Image_2.jpeg │ └── Test_Image_3.jpg │ └── XYZTests.swift ├── LICENSE ├── Package.swift └── README.md /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test: 11 | name: Test 12 | runs-on: macOS-latest 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v2 16 | - name: Run tests 17 | run: xcodebuild test -project ColorKit/ColorKit.xcodeproj -scheme ColorKit -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 11" 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Assets/color_difference_deltaE_CIE94.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/Assets/color_difference_deltaE_CIE94.jpg -------------------------------------------------------------------------------- /Assets/color_difference_deltaE_RGB.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/Assets/color_difference_deltaE_RGB.jpg -------------------------------------------------------------------------------- /Assets/color_palette_albums.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/Assets/color_palette_albums.jpg -------------------------------------------------------------------------------- /Assets/colorkit_banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/Assets/colorkit_banner.jpg -------------------------------------------------------------------------------- /Assets/dominant_colors.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/Assets/dominant_colors.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ColorKit/ColorKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ColorKit/ColorKit.xcodeproj/xcshareddata/xcbaselines/A181C8602404728F0008D4B7.xcbaseline/FD9A0D44-3456-44B1-ADA8-6380851AC44B.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | classNames 6 | 7 | DominantColorsTests 8 | 9 | testRealImage() 10 | 11 | com.apple.XCTPerformanceMetric_WallClockTime 12 | 13 | baselineAverage 14 | 0.189 15 | baselineIntegrationDisplayName 16 | Local Baseline 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ColorKit/ColorKit.xcodeproj/xcshareddata/xcbaselines/A181C8602404728F0008D4B7.xcbaseline/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | runDestinationsByUUID 6 | 7 | FD9A0D44-3456-44B1-ADA8-6380851AC44B 8 | 9 | localComputer 10 | 11 | busSpeedInMHz 12 | 100 13 | cpuCount 14 | 1 15 | cpuKind 16 | Quad-Core Intel Core i7 17 | cpuSpeedInMHz 18 | 2900 19 | logicalCPUCoresPerPackage 20 | 8 21 | modelCode 22 | MacBookPro13,3 23 | physicalCPUCoresPerPackage 24 | 4 25 | platformIdentifier 26 | com.apple.platform.macosx 27 | 28 | targetArchitecture 29 | x86_64 30 | targetDevice 31 | 32 | modelCode 33 | iPhone12,1 34 | platformIdentifier 35 | com.apple.platform.iphonesimulator 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /ColorKit/ColorKit.xcodeproj/xcshareddata/xcschemes/ColorKit.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /ColorKit/ColorKit.xcodeproj/xcshareddata/xcschemes/ColorKitSampleApp.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /ColorKit/ColorKit/AverageColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AverageColor.swift 3 | // ColorKit 4 | // 5 | // Created by Boris Emorine on 5/15/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreImage 11 | 12 | 13 | extension UIImage { 14 | 15 | enum ImageColorError: Error { 16 | /// The `CIImage` instance could not be created. 17 | case ciImageFailure 18 | 19 | /// The `CGImage` instance could not be created. 20 | case cgImageFailure 21 | 22 | /// Failed to get the pixel data from the `CGImage` instance. 23 | case cgImageDataFailure 24 | 25 | /// An error happened during the creation of the image after applying the filter. 26 | case outputImageFailure 27 | 28 | var localizedDescription: String { 29 | switch self { 30 | case .ciImageFailure: 31 | return "Failed to get a `CIImage` instance." 32 | case .cgImageFailure: 33 | return "Failed to get a `CGImage` instance." 34 | case .cgImageDataFailure: 35 | return "Failed to get image data." 36 | case .outputImageFailure: 37 | return "Could not get the output image from the filter." 38 | } 39 | } 40 | } 41 | 42 | /// Computes the average color of the image. 43 | public func averageColor() throws -> UIColor { 44 | guard let ciImage = CIImage(image: self) else { 45 | throw ImageColorError.ciImageFailure 46 | } 47 | 48 | guard let areaAverageFilter = CIFilter(name: "CIAreaAverage") else { 49 | fatalError("Could not create `CIAreaAverage` filter.") 50 | } 51 | 52 | areaAverageFilter.setValue(ciImage, forKey: kCIInputImageKey) 53 | areaAverageFilter.setValue(CIVector(cgRect: ciImage.extent), forKey: "inputExtent") 54 | 55 | guard let outputImage = areaAverageFilter.outputImage else { 56 | throw ImageColorError.outputImageFailure 57 | } 58 | 59 | let context = CIContext() 60 | var bitmap = [UInt8](repeating: 0, count: 4) 61 | 62 | context.render(outputImage, toBitmap: &bitmap, rowBytes: 4, bounds: CGRect(x: 0, y: 0, width: 1, height: 1), format: CIFormat.RGBA8, colorSpace: CGColorSpaceCreateDeviceRGB()) 63 | 64 | let averageColor = UIColor(red: CGFloat(bitmap[0]) / 255.0, green: CGFloat(bitmap[1]) / 255.0, blue: CGFloat(bitmap[2]) / 255.0, alpha: CGFloat(bitmap[3]) / 255.0) 65 | 66 | return averageColor 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /ColorKit/ColorKit/CMYK.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CMYK.swift 3 | // ColorKit 4 | // 5 | // Created by Boris Emorine on 6/20/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | 13 | /// The cyan (C) channel of the CMYK color space. 14 | public var cyan: CGFloat { 15 | return (1 - red - key) / (1 - key) 16 | } 17 | 18 | /// The magenta (M) channel of the CMYK color space. 19 | public var magenta: CGFloat { 20 | return (1 - green - key) / (1 - key) 21 | } 22 | 23 | /// The yellow (Y) channel of the CMYK color space. 24 | public var yellow: CGFloat { 25 | return (1 - blue - key) / (1 - key) 26 | } 27 | 28 | /// The key (black) (K) channel of the CMYK color space. 29 | public var key: CGFloat { 30 | return 1 - max(red, green, blue) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /ColorKit/ColorKit/ColorKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // ColorKit.h 3 | // ColorKit 4 | // 5 | // Created by Boris Emorine on 2/24/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for ColorKit. 12 | FOUNDATION_EXPORT double ColorKitVersionNumber; 13 | 14 | //! Project version string for ColorKit. 15 | FOUNDATION_EXPORT const unsigned char ColorKitVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /ColorKit/ColorKit/ColorPalette.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorPalette.swift 3 | // ColorKit 4 | // 5 | // Created by Boris Emorine on 7/6/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// A simple structure used to represent color palettes. 12 | public struct ColorPalette { 13 | 14 | /// The color that should be used as a background. 15 | public let background: UIColor 16 | 17 | /// The color that should be used as the primary detail. For example the main text. 18 | public let primary: UIColor 19 | 20 | /// The color that should be used as the secondary detail. For example text that isn't as important as the one represented by the primary property. 21 | public let secondary: UIColor? 22 | 23 | /// Initializes a coherant color palette based on the passed in colors. 24 | /// The colors should be sorted by order of importance, where the first color is the most important. 25 | /// This makes it easy to generate palettes from a collection of colors. 26 | /// 27 | /// - Parameters: 28 | /// - orderedColors: The colors that will be used to generate the color palette. The first color is considered the most important one. 29 | /// - darkBackground: Whether the color palette is required to have a dark background. If set to false, the background can be dark or bright. 30 | /// - ignoreContrastRatio: Whether the color paletter should ignore the contrast ratio between the different colors. It is recommended to set this value to `false` (default) if the color paletter will be used to display text. 31 | public init?(orderedColors: [UIColor], darkBackground: Bool = true, ignoreContrastRatio: Bool = false) { 32 | guard orderedColors.count > 1 else { 33 | return nil 34 | } 35 | 36 | var backgroundColor: UIColor 37 | if darkBackground { 38 | guard let darkestOrderedColor = orderedColors.first(where: { color -> Bool in 39 | return color.relativeLuminance < 0.5 40 | }) else { 41 | return nil 42 | } 43 | backgroundColor = darkestOrderedColor 44 | } else { 45 | backgroundColor = orderedColors.first! 46 | } 47 | 48 | var primaryColor: UIColor? 49 | var secondaryColor: UIColor? 50 | if !ignoreContrastRatio { 51 | orderedColors.forEach { (color) in 52 | guard color != backgroundColor else { return } 53 | 54 | let contrastRatio = backgroundColor.contrastRatio(with: color) 55 | switch contrastRatio { 56 | case .acceptable, .acceptableForLargeText: 57 | if primaryColor != nil { 58 | secondaryColor = nil 59 | } else { 60 | primaryColor = color 61 | } 62 | default: 63 | return 64 | } 65 | } 66 | } else { 67 | orderedColors.forEach { (color) in 68 | guard color != backgroundColor else { return } 69 | if primaryColor == nil { 70 | primaryColor = color 71 | } else { 72 | secondaryColor = color 73 | } 74 | } 75 | } 76 | 77 | guard let primary = primaryColor else { return nil } 78 | self.background = backgroundColor 79 | self.primary = primary 80 | self.secondary = secondaryColor 81 | } 82 | 83 | /// Initializes a coherant color palette based on the passed in colors. 84 | /// This makes it easy to generate palettes from a collection of colors. 85 | /// 86 | /// - Parameters: 87 | /// - colors: The colors that will be used to generate the color palette. The best colors will be selected to have a color palette with enough contrast. At least two colors should be passed in. 88 | /// - darkBackground: Whether the color palette is required to have a dark background. If set to false, the background can be dark or bright. 89 | /// - ignoreContrastRatio: Whether the color paletter should ignore the contrast ratio between the different colors. It is recommended to set this value to `false` (default) if the color paletter will be used to display text. 90 | public init?(colors: [UIColor], darkBackground: Bool = true, ignoreContrastRatio: Bool = false) { 91 | guard colors.count > 1 else { 92 | return nil 93 | } 94 | 95 | var darkestColor: UIColor? 96 | var brightestColor: UIColor? 97 | 98 | colors.forEach { (color) in 99 | if color.relativeLuminance < darkestColor?.relativeLuminance ?? .greatestFiniteMagnitude { 100 | darkestColor = color 101 | } 102 | 103 | if color.relativeLuminance > brightestColor?.relativeLuminance ?? .leastNormalMagnitude { 104 | brightestColor = color 105 | } 106 | } 107 | guard let darkestColor = darkestColor, 108 | let brightestColor = brightestColor else { 109 | return nil 110 | } 111 | 112 | if !ignoreContrastRatio { 113 | let backgroundPrimaryContrastRatio = darkestColor.contrastRatio(with: brightestColor) 114 | switch backgroundPrimaryContrastRatio { 115 | case .acceptable, .acceptableForLargeText: 116 | break 117 | default: 118 | return nil 119 | } 120 | } 121 | 122 | let backgroundColor = darkBackground ? darkestColor : brightestColor 123 | let primaryColor = darkBackground ? brightestColor : darkestColor 124 | 125 | let secondaryColor = colors.first { (color) -> Bool in 126 | if !ignoreContrastRatio { 127 | let backgroundColorConstratRatio = color.contrastRatio(with: backgroundColor) 128 | switch backgroundColorConstratRatio { 129 | case .acceptable , .acceptableForLargeText: 130 | break 131 | default: return false 132 | } 133 | } 134 | 135 | let backgroundColorDifference = color.difference(from: backgroundColor) 136 | switch backgroundColorDifference { 137 | case .different, .far: 138 | let primaryColorDifference = color.difference(from: primaryColor) 139 | switch primaryColorDifference { 140 | case .near, .different, .far: 141 | return true 142 | default: 143 | return false 144 | } 145 | default: 146 | return false 147 | } 148 | } 149 | 150 | self.background = backgroundColor 151 | self.primary = primaryColor 152 | self.secondary = secondaryColor 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /ColorKit/ColorKit/ComplementaryColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ComplementaryColor.swift 3 | // ColorKit 4 | // 5 | // Created by Boris Emorine on 3/18/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UIColor { 13 | 14 | /// Computes the complementary color of the current color instance. 15 | /// Complementary colors are opposite on the color wheel. 16 | public var complementaryColor: UIColor { 17 | return UIColor(red: (255.0 - red255) / 255.0, 18 | green: (255.0 - green255) / 255.0, 19 | blue: (255.0 - blue255) / 255.0, alpha: alpha) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /ColorKit/ColorKit/ContrastRatio.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContrastRatio.swift 3 | // ColorKit 4 | // 5 | // Created by Boris Emorine on 3/13/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | 13 | /// An enumeration which groups contrast ratios based on their readability. 14 | /// This follows the Web Content Accessibility Guidelines (WCAG) 2.0. 15 | public enum ContrastRatioResult { 16 | /// The contrast ratio between is enough for most people to distinguish the two colors. 17 | /// It can be used as text / background. 18 | case acceptable(CGFloat) 19 | 20 | /// The contrast ratio is not big enough for most people to distinguish the two colors. 21 | /// It should only be used for large text / background. 22 | case acceptableForLargeText(CGFloat) 23 | 24 | /// The contrast ratio between the two colors is low. 25 | /// It will be difficult for most to distinguish the two colors easily. 26 | /// Do not use these two colors as text / background. 27 | case low(CGFloat) 28 | 29 | init(value: CGFloat) { 30 | if value >= 4.5 { 31 | self = .acceptable(value) 32 | } else if value >= 3.0 { 33 | self = .acceptableForLargeText(value) 34 | } else { 35 | self = .low(value) 36 | } 37 | } 38 | 39 | var associatedValue: CGFloat { 40 | switch self { 41 | case .acceptable(let value), 42 | .acceptableForLargeText(let value), 43 | .low(let value): 44 | return value 45 | } 46 | } 47 | } 48 | 49 | /// Computes the contrast ratio between the current color instance, and the one passed in. 50 | /// Contrast ratios can range from 1 to 21 (commonly written 1:1 to 21:1). 51 | public func contrastRatio(with color: UIColor) -> ContrastRatioResult { 52 | let l1 = max(color.relativeLuminance, relativeLuminance) 53 | let l2 = min(color.relativeLuminance, relativeLuminance) 54 | 55 | let contrastRatio = (l1 + 0.05) / (l2 + 0.05) 56 | return ContrastRatioResult(value: contrastRatio.rounded(.toNearestOrEven, precision: 100)) 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /ColorKit/ColorKit/Difference.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Comparison.swift 3 | // ColorKit 4 | // 5 | // Created by Boris Emorine on 2/24/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | 13 | public enum ColorDifferenceResult: Comparable { 14 | 15 | /// There is no difference between the two colors. 16 | case indentical(CGFloat) 17 | 18 | /// The difference between the two colors is not perceptible by human eye. 19 | case similar(CGFloat) 20 | 21 | /// The difference between the two colors is perceptible through close observation. 22 | case close(CGFloat) 23 | 24 | /// The difference between the two colors is perceptible at a glance. 25 | case near(CGFloat) 26 | 27 | /// The two colors are different, but not opposite. 28 | case different(CGFloat) 29 | 30 | /// The two colors are more opposite than similar. 31 | case far(CGFloat) 32 | 33 | init(value: CGFloat) { 34 | if value == 0 { 35 | self = .indentical(value) 36 | } else if value <= 1.0 { 37 | self = .similar(value) 38 | } else if value <= 2.0 { 39 | self = .close(value) 40 | } else if value <= 10.0 { 41 | self = .near(value) 42 | } else if value <= 50.0 { 43 | self = .different(value) 44 | } else { 45 | self = .far(value) 46 | } 47 | } 48 | 49 | var associatedValue: CGFloat { 50 | switch self { 51 | case .indentical(let value), 52 | .similar(let value), 53 | .close(let value), 54 | .near(let value), 55 | .different(let value), 56 | .far(let value): 57 | return value 58 | } 59 | } 60 | 61 | public static func < (lhs: UIColor.ColorDifferenceResult, rhs: UIColor.ColorDifferenceResult) -> Bool { 62 | return lhs.associatedValue < rhs.associatedValue 63 | } 64 | 65 | } 66 | 67 | /// The different algorithms for comparing colors. 68 | /// @see https://en.wikipedia.org/wiki/Color_difference 69 | public enum DeltaEFormula { 70 | /// The euclidean algorithm is the simplest and fastest one, but will yield results that are unexpected to the human eye. Especially in the green range. 71 | /// It simply calculates the euclidean distance in the RGB color space. 72 | case euclidean 73 | 74 | /// The `CIE76`algorithm is fast and yields acceptable results in most scenario. 75 | case CIE76 76 | 77 | /// The `CIE94` algorithm is an improvement to the `CIE76`, especially for the saturated regions. It's marginally slower than `CIE76`. 78 | case CIE94 79 | 80 | /// The `CIEDE2000` algorithm is the most precise algorithm to compare colors. 81 | /// It is considerably slower than its predecessors. 82 | case CIEDE2000 83 | } 84 | 85 | /// Computes the difference between the passed in `UIColor` instance. 86 | /// 87 | /// - Parameters: 88 | /// - color: The color to compare this instance to. 89 | /// - formula: The algorithm to use to make the comparaison. 90 | /// - Returns: The different between the passed in `UIColor` instance and this instance. 91 | public func difference(from color: UIColor, using formula: DeltaEFormula = .CIE94) -> ColorDifferenceResult { 92 | switch formula { 93 | case .euclidean: 94 | let differenceValue = sqrt(pow(self.red255 - color.red255, 2) + pow(self.green255 - color.green255, 2) + pow(self.blue255 - color.blue255, 2)) 95 | let roundedDifferenceValue = differenceValue.rounded(.toNearestOrEven, precision: 100) 96 | return ColorDifferenceResult(value: roundedDifferenceValue) 97 | case .CIE76: 98 | let differenceValue = sqrt(pow(color.L - self.L, 2) + pow(color.a - self.a, 2) + pow(color.b - self.b, 2)) 99 | let roundedDifferenceValue = differenceValue.rounded(.toNearestOrEven, precision: 100) 100 | return ColorDifferenceResult(value: roundedDifferenceValue) 101 | case .CIE94: 102 | let differenceValue = UIColor.deltaECIE94(lhs: self, rhs: color) 103 | let roundedDifferenceValue = differenceValue.rounded(.toNearestOrEven, precision: 100) 104 | return ColorDifferenceResult(value: roundedDifferenceValue) 105 | default: 106 | return ColorDifferenceResult(value: -1) 107 | } 108 | } 109 | 110 | private static func deltaECIE94(lhs: UIColor, rhs: UIColor) -> CGFloat { 111 | let kL: CGFloat = 1.0 112 | let kC: CGFloat = 1.0 113 | let kH: CGFloat = 1.0 114 | let k1: CGFloat = 0.045 115 | let k2: CGFloat = 0.015 116 | let sL: CGFloat = 1.0 117 | 118 | let c1 = sqrt(pow(lhs.a, 2) + pow(lhs.b, 2)) 119 | let sC = 1 + k1 * c1 120 | let sH = 1 + k2 * c1 121 | 122 | let deltaL = lhs.L - rhs.L 123 | let deltaA = lhs.a - rhs.a 124 | let deltaB = lhs.b - rhs.b 125 | 126 | let c2 = sqrt(pow(rhs.a, 2) + pow(rhs.b, 2)) 127 | let deltaCab = c1 - c2 128 | 129 | let deltaHab = sqrt(pow(deltaA, 2) + pow(deltaB, 2) - pow(deltaCab, 2)) 130 | 131 | let p1 = pow(deltaL / (kL * sL), 2) 132 | let p2 = pow(deltaCab / (kC * sC), 2) 133 | let p3 = pow(deltaHab / (kH * sH), 2) 134 | 135 | let deltaE = sqrt(p1 + p2 + p3) 136 | 137 | return deltaE; 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /ColorKit/ColorKit/DominantColors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DominantColors.swift 3 | // ColorKit 4 | // 5 | // Created by Boris Emorine on 5/19/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreImage 11 | 12 | /// A simple structure containing a color, and a frequency. 13 | public class ColorFrequency: CustomStringConvertible { 14 | 15 | /// A simple `UIColor` instance. 16 | public let color: UIColor 17 | 18 | /// The frequency of the color. 19 | /// That is, how much it is present. 20 | public var frequency: CGFloat 21 | 22 | public var description: String { 23 | return "Color: \(color) - Frequency: \(frequency)" 24 | } 25 | 26 | init(color: UIColor, count: CGFloat) { 27 | self.frequency = count 28 | self.color = color 29 | } 30 | } 31 | 32 | extension UIImage { 33 | 34 | public enum DominantColorAlgorithm { 35 | 36 | /// Finds the dominant colors of an image by iterating, grouping and sorting its pixels. 37 | case iterative 38 | 39 | /// Finds the dominant colors of an image by using using a k-means clustering algorithm. 40 | case kMeansClustering 41 | } 42 | 43 | /// Reoresents how precise the dominant color algorithm should be. 44 | /// The lower the quality, the faster the algorithm. 45 | /// `.best` should only be reserved for very small images. 46 | public enum DominantColorQuality { 47 | case low 48 | case fair 49 | case high 50 | case best 51 | 52 | var prefferedImageArea: CGFloat? { 53 | switch self { 54 | case .low: 55 | return 1_000 56 | case .fair: 57 | return 10_000 58 | case .high: 59 | return 100_000 60 | case .best: 61 | return nil 62 | } 63 | } 64 | 65 | var kMeansInputPasses: Int { 66 | switch self { 67 | case .low: 68 | return 1 69 | case .fair: 70 | return 10 71 | case .high: 72 | return 15 73 | case .best: 74 | return 20 75 | } 76 | } 77 | 78 | /// Returns a new size (with the same aspect ratio) that takes into account the quality to match. 79 | /// For example with a `.low` quality, the returned size will be much smaller. 80 | /// On the opposite, with a `.best` quality, the returned size will be identical to the original size. 81 | func targetSize(for originalSize: CGSize) -> CGSize { 82 | guard let prefferedImageArea = prefferedImageArea else { 83 | return originalSize 84 | } 85 | 86 | let originalArea = originalSize.area 87 | 88 | guard originalArea > prefferedImageArea else { 89 | return originalSize 90 | } 91 | 92 | return originalSize.transformToFit(in: prefferedImageArea) 93 | } 94 | } 95 | 96 | /// Attempts to computes the dominant colors of the image. 97 | /// This is not the absolute dominent colors, but instead colors that are similar are groupped together. 98 | /// This avoids having to deal with many shades of the same colors, which are frequent when dealing with compression artifacts (jpeg etc.). 99 | /// - Parameters: 100 | /// - quality: The quality used to determine the dominant colors. A higher quality will yield more accurate results, but will be slower. 101 | /// - algorithm: The algorithm used to determine the dominant colors. When using a k-means algorithm (`kMeansClustering`), a `CIKMeans` CIFilter isused. Unfortunately this filter doesn't work on the simulator. 102 | /// - Returns: The dominant colors as array of `UIColor` instances. When using the `.iterative` algorithm, this array is ordered where the first color is the most dominant one. 103 | public func dominantColors(with quality: DominantColorQuality = .fair, algorithm: DominantColorAlgorithm = .iterative) throws -> [UIColor] { 104 | switch algorithm { 105 | case .iterative: 106 | let dominantColorFrequencies = try self.dominantColorFrequencies(with: quality) 107 | let dominantColors = dominantColorFrequencies.map { (colorFrequency) -> UIColor in 108 | return colorFrequency.color 109 | } 110 | 111 | return dominantColors 112 | case .kMeansClustering: 113 | let dominantcolors = try kMeansClustering(with: quality) 114 | return dominantcolors 115 | } 116 | } 117 | 118 | /// Attempts to computes the dominant colors of the image. 119 | /// This is not the absolute dominent colors, but instead colors that are similar are groupped together. 120 | /// This avoid having to deal with many shades of the same colors, which are frequent when dealing with compression artifacts (jpeg etc.). 121 | /// - Parameters: 122 | /// - quality: The quality used to determine the dominant colors. A higher quality will yield more accurate results, but will be slower. 123 | /// - Returns: The dominant colors as an ordered array of `ColorFrequency` instances, where the first element is the most common one. The frequency is represented as a percentage ranging from 0 to 1. 124 | public func dominantColorFrequencies(with quality: DominantColorQuality = .fair) throws -> [ColorFrequency] { 125 | 126 | // ------ 127 | // Step 1: Resize the image based on the requested quality 128 | // ------ 129 | 130 | let targetSize = quality.targetSize(for: resolution) 131 | 132 | let resizedImage = resize(to: targetSize) 133 | guard let cgImage = resizedImage.cgImage else { 134 | throw ImageColorError.cgImageFailure 135 | } 136 | 137 | let cfData = cgImage.dataProvider!.data 138 | guard let data = CFDataGetBytePtr(cfData) else { 139 | throw ImageColorError.cgImageDataFailure 140 | } 141 | 142 | // ------ 143 | // Step 2: Add each pixel to a NSCountedSet. This will give us a count for each color. 144 | // ------ 145 | 146 | let colorsCountedSet = NSCountedSet(capacity: Int(targetSize.area)) 147 | 148 | struct RGB: Hashable { 149 | let R: UInt8 150 | let G: UInt8 151 | let B: UInt8 152 | } 153 | 154 | for yCoordonate in 0 ..< cgImage.height { 155 | for xCoordonate in 0 ..< cgImage.width { 156 | let index = (cgImage.width * yCoordonate + xCoordonate) * 4 157 | 158 | // Let's make sure there is enough alpha. 159 | guard data[index + 3] > 150 else { continue } 160 | 161 | let pixelColor = RGB(R: data[index + 0], G: data[index + 1], B: data[index + 2]) 162 | colorsCountedSet.add(pixelColor) 163 | } 164 | } 165 | 166 | // ------ 167 | // Step 3: Remove colors that are barely present on the image. 168 | // ------ 169 | 170 | let minCountThreshold = Int(targetSize.area * (0.01 / 100.0)) 171 | 172 | let filteredColorsCountMap = colorsCountedSet.compactMap { (rgb) -> ColorFrequency? in 173 | let count = colorsCountedSet.count(for: rgb) 174 | 175 | guard count > minCountThreshold else { 176 | return nil 177 | } 178 | 179 | let rgb = rgb as! RGB 180 | 181 | return ColorFrequency(color: UIColor(red: CGFloat(rgb.R) / 255.0, green: CGFloat(rgb.G) / 255.0, blue: CGFloat(rgb.B) / 255.0, alpha: 1.0), count: CGFloat(count)) 182 | } 183 | 184 | // ------ 185 | // Step 4: Sort the remaning colors by frequency. 186 | // ------ 187 | 188 | let sortedColorsFrequencies = filteredColorsCountMap.sorted { (lhs, rhs) -> Bool in 189 | return lhs.frequency > rhs.frequency 190 | } 191 | 192 | // ------ 193 | // Step 5: Only keep the most frequent colors. 194 | // ------ 195 | 196 | let maxNumberOfColors = 500 197 | let colorFrequencies = sortedColorsFrequencies.prefix(maxNumberOfColors) 198 | 199 | // ------ 200 | // Step 6: Combine similar colors together. 201 | // ------ 202 | 203 | /// The main dominant colors on the picture. 204 | var dominantColors = [ColorFrequency]() 205 | 206 | /// The score that needs to be met to consider two colors similar. 207 | let colorDifferenceScoreThreshold: CGFloat = 10.0 208 | 209 | // Combines colors that are similar. 210 | for colorFrequency in colorFrequencies { 211 | var bestMatchScore: CGFloat? 212 | var bestMatchColorFrequency: ColorFrequency? 213 | for dominantColor in dominantColors { 214 | let differenceScore = colorFrequency.color.difference(from: dominantColor.color, using: .CIE76).associatedValue 215 | if differenceScore < bestMatchScore ?? CGFloat(Int.max) { 216 | bestMatchScore = differenceScore 217 | bestMatchColorFrequency = dominantColor 218 | } 219 | } 220 | 221 | if let bestMatchScore = bestMatchScore, bestMatchScore < colorDifferenceScoreThreshold { 222 | bestMatchColorFrequency!.frequency += 1 223 | } else { 224 | dominantColors.append(colorFrequency) 225 | } 226 | } 227 | 228 | // ------ 229 | // Step 7: Again, limit the number of colors we keep, this time drastically. 230 | // ------ 231 | 232 | // We only keep the first few dominant colors. 233 | let dominantColorsMaxCount = 8 234 | dominantColors = Array(dominantColors.prefix(dominantColorsMaxCount)) 235 | 236 | // ------ 237 | // Step 8: Sort again on frequencies because the order may have changed because we combined colors. 238 | // ------ 239 | 240 | dominantColors = dominantColors.sorted(by: { (lhs, rhs) -> Bool in 241 | return lhs.frequency > rhs.frequency 242 | }) 243 | 244 | // ------ 245 | // Step 9: Calculate the frequency of colors as a percentage. 246 | // ------ 247 | 248 | /// The total count of colors 249 | let dominantColorsTotalCount = dominantColors.reduce(into: 0) { (result, colorFrequency) in 250 | result += colorFrequency.frequency 251 | } 252 | 253 | dominantColors = dominantColors.map({ (colorFrequency) -> ColorFrequency in 254 | let percentage = (100.0 / (dominantColorsTotalCount / colorFrequency.frequency) / 100.0).rounded(.up, precision: 100) 255 | 256 | return ColorFrequency(color: colorFrequency.color, count: percentage) 257 | }) 258 | 259 | return dominantColors 260 | } 261 | 262 | private func kMeansClustering(with quality: DominantColorQuality) throws -> [UIColor] { 263 | guard let ciImage = CIImage(image: self) else { 264 | throw ImageColorError.ciImageFailure 265 | } 266 | let kMeansFilter = CIFilter(name: "CIKMeans")! 267 | 268 | let clusterCount = 8 269 | 270 | kMeansFilter.setValue(ciImage, forKey: kCIInputImageKey) 271 | kMeansFilter.setValue(CIVector(cgRect: ciImage.extent), forKey: "inputExtent") 272 | kMeansFilter.setValue(clusterCount, forKey: "inputCount") 273 | kMeansFilter.setValue(quality.kMeansInputPasses, forKey: "inputPasses") 274 | kMeansFilter.setValue(NSNumber(value: true), forKey: "inputPerceptual") 275 | 276 | guard var outputImage = kMeansFilter.outputImage else { 277 | throw ImageColorError.outputImageFailure 278 | } 279 | 280 | outputImage = outputImage.settingAlphaOne(in: outputImage.extent) 281 | 282 | let context = CIContext() 283 | var bitmap = [UInt8](repeating: 0, count: 4 * clusterCount) 284 | 285 | context.render(outputImage, toBitmap: &bitmap, rowBytes: 4 * clusterCount, bounds: outputImage.extent, format: CIFormat.RGBA8, colorSpace: ciImage.colorSpace!) 286 | 287 | var dominantColors = [UIColor]() 288 | 289 | for i in 0.. CGFloat { 15 | return (self * CGFloat(precision)).rounded(rule) / CGFloat(precision) 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /ColorKit/ColorKit/Extensions/CGSizeExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGSizeExtensions.swift 3 | // ColorKit 4 | // 5 | // Created by Boris Emorine on 5/30/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import CoreGraphics 10 | 11 | extension CGSize { 12 | 13 | /// The area of the size. 14 | var area: CGFloat { 15 | return width * height 16 | } 17 | 18 | /// Returns a new size of the target area, keeping the same aspect ratio. 19 | func transformToFit(in targetArea: CGFloat) -> CGSize { 20 | let ratio = area / targetArea 21 | let targetSize = CGSize(width: width / sqrt(ratio), height: height / sqrt(ratio)) 22 | 23 | return targetSize 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /ColorKit/ColorKit/Extensions/UIImageExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImageExtensions.swift 3 | // ColorKit 4 | // 5 | // Created by Boris Emorine on 5/30/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIImage { 12 | 13 | var resolution: CGSize { 14 | return CGSize(width: size.width * scale, height: size.height * scale) 15 | } 16 | 17 | func resize(to targetSize: CGSize) -> UIImage { 18 | guard targetSize != resolution else { 19 | return self 20 | } 21 | 22 | let format = UIGraphicsImageRendererFormat() 23 | format.scale = 1 24 | format.opaque = true 25 | let renderer = UIGraphicsImageRenderer(size: targetSize, format: format) 26 | let resizedImage = renderer.image { _ in 27 | self.draw(in: CGRect(origin: CGPoint.zero, size: targetSize)) 28 | } 29 | 30 | return resizedImage 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /ColorKit/ColorKit/Hex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Hex.swift 3 | // ColorKit 4 | // 5 | // Created by Boris Emorine on 2/27/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | 13 | /// Convenience initializer with hexadecimal values. 14 | public convenience init?(hex: String) { 15 | let hexString = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) 16 | 17 | var hexValue = UInt64() 18 | 19 | guard Scanner(string: hexString).scanHexInt64(&hexValue) else { 20 | return nil 21 | } 22 | 23 | let a, r, g, b: UInt64 24 | switch hexString.count { 25 | case 3: // 0xRGB 26 | (a, r, g, b) = (255, (hexValue >> 8) * 17, (hexValue >> 4 & 0xF) * 17, (hexValue & 0xF) * 17) 27 | case 6: // 0xRRGGBB 28 | (a, r, g, b) = (255, hexValue >> 16, hexValue >> 8 & 0xFF, hexValue & 0xFF) 29 | case 8: // 0xRRGGBBAA 30 | (r, g, b, a) = (hexValue >> 24, hexValue >> 16 & 0xFF, hexValue >> 8 & 0xFF, hexValue & 0xFF) 31 | default: 32 | (a, r, g, b) = (255, 0, 0, 0) 33 | } 34 | self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255) 35 | } 36 | 37 | /// The hexadecimal value of the color. 38 | public var hex: String { 39 | let rgb: Int = (Int)(self.red * 255) << 16 | (Int)(self.green * 255) << 8 | (Int)(self.blue * 255) << 0 40 | return String(format: "#%06x", rgb) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /ColorKit/ColorKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /ColorKit/ColorKit/Lab.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Lab.swift 3 | // ColorKit 4 | // 5 | // Created by Boris Emorine on 2/26/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct Lab { 12 | let L: CGFloat 13 | let a: CGFloat 14 | let b: CGFloat 15 | } 16 | 17 | struct LabCalculator { 18 | static func convert(RGB: RGB) -> Lab { 19 | let XYZ = XYZCalculator.convert(rgb: RGB) 20 | let Lab = LabCalculator.convert(XYZ: XYZ) 21 | return Lab 22 | } 23 | 24 | static let referenceX: CGFloat = 95.047 25 | static let referenceY: CGFloat = 100.0 26 | static let referenceZ: CGFloat = 108.883 27 | 28 | static func convert(XYZ: XYZ) -> Lab { 29 | func transform(value: CGFloat) -> CGFloat { 30 | if value > 0.008856 { 31 | return pow(value, 1 / 3) 32 | } else { 33 | return (7.787 * value) + (16 / 116) 34 | } 35 | } 36 | 37 | let X = transform(value: XYZ.X / referenceX) 38 | let Y = transform(value: XYZ.Y / referenceY) 39 | let Z = transform(value: XYZ.Z / referenceZ) 40 | 41 | let L = ((116.0 * Y) - 16.0).rounded(.toNearestOrEven, precision: 100) 42 | let a = (500.0 * (X - Y)).rounded(.toNearestOrEven, precision: 100) 43 | let b = (200.0 * (Y - Z)).rounded(.toNearestOrEven, precision: 100) 44 | 45 | return Lab(L: L, a: a, b: b) 46 | } 47 | } 48 | 49 | extension UIColor { 50 | 51 | /// The L* value of the CIELAB color space. 52 | /// L* represents the lightness of the color from 0 (black) to 100 (white). 53 | public var L: CGFloat { 54 | let Lab = LabCalculator.convert(RGB: self.rgb) 55 | return Lab.L 56 | } 57 | 58 | /// The a* value of the CIELAB color space. 59 | /// a* represents colors from green to red. 60 | public var a: CGFloat { 61 | let Lab = LabCalculator.convert(RGB: self.rgb) 62 | return Lab.a 63 | } 64 | 65 | /// The b* value of the CIELAB color space. 66 | /// b* represents colors from blue to yellow. 67 | public var b: CGFloat { 68 | let Lab = LabCalculator.convert(RGB: self.rgb) 69 | return Lab.b 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /ColorKit/ColorKit/Localization/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | ColorNames.strings 3 | ColorKit 4 | 5 | Created by Boris Emorine on 12/11/20. 6 | Copyright © 2020 BorisEmorine. All rights reserved. 7 | */ 8 | 9 | "colorkit.color.name.primary.red" = "red"; 10 | 11 | "colorkit.color.name.primary.green" = "green"; 12 | 13 | "colorkit.color.name.primary.blue" = "blue"; 14 | 15 | "colorkit.color.name.secondary.yellow" = "yellow"; 16 | 17 | "colorkit.color.name.secondary.magenta" = "magenta"; 18 | 19 | "colorkit.color.name.secondary.cyan" = "cyan"; 20 | 21 | "colorkit.color.name.tertiary.azure" = "azure"; 22 | 23 | "colorkit.color.name.tertiary.violet" = "violet"; 24 | 25 | "colorkit.color.name.tertiary.rose" = "rose"; 26 | 27 | "colorkit.color.name.tertiary.orange" = "orange"; 28 | 29 | "colorkit.color.name.tertiary.chartreuse" = "chartreuse"; 30 | 31 | "colorkit.color.name.tertiary.spring_green" = "spring green"; 32 | 33 | "colorkit.color.name.gray_shade.black" = "black"; 34 | 35 | "colorkit.color.name.gray_shade.white" = "white"; 36 | 37 | "colorkit.color.name.gray_shade.gray" = "gray"; 38 | -------------------------------------------------------------------------------- /ColorKit/ColorKit/Localization/fr.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | ColorNames.strings 3 | ColorKit 4 | 5 | Created by Boris Emorine on 12/11/20. 6 | Copyright © 2020 BorisEmorine. All rights reserved. 7 | */ 8 | 9 | "colorkit.color.name.primary.red" = "rouge"; 10 | 11 | "colorkit.color.name.primary.green" = "vert"; 12 | 13 | "colorkit.color.name.primary.blue" = "bleu"; 14 | 15 | "colorkit.color.name.secondary.yellow" = "jaune"; 16 | 17 | "colorkit.color.name.secondary.magenta" = "magenta"; 18 | 19 | "colorkit.color.name.secondary.cyan" = "cyan"; 20 | 21 | "colorkit.color.name.tertiary.azure" = "bleu roi"; 22 | 23 | "colorkit.color.name.tertiary.violet" = "violet"; 24 | 25 | "colorkit.color.name.tertiary.rose" = "rose"; 26 | 27 | "colorkit.color.name.tertiary.orange" = "orange"; 28 | 29 | "colorkit.color.name.tertiary.chartreuse" = "lime"; 30 | 31 | "colorkit.color.name.tertiary.spring_green" = "vert menthe"; 32 | 33 | "colorkit.color.name.gray_shade.black" = "noir"; 34 | 35 | "colorkit.color.name.gray_shade.white" = "blanc"; 36 | 37 | "colorkit.color.name.gray_shade.gray" = "gris"; 38 | -------------------------------------------------------------------------------- /ColorKit/ColorKit/Name.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Name.swift 3 | // ColorKit 4 | // 5 | // Created by Boris Emorine on 12/9/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | 13 | /// This function gives a readable name to the current `UIColor` instance. 14 | /// Warning: some languages may not be supported. 15 | /// - Returns: The name of the color. 16 | public func name() -> String { 17 | let colorList = NamedColorList.BasicColors 18 | var closestColor: (UIColor, String)? 19 | var bestMatch: UIColor.ColorDifferenceResult = .init(value: CGFloat.infinity) 20 | 21 | for color in colorList { 22 | let difference = self.difference(from: color.0, using: .CIE94) 23 | 24 | if difference < bestMatch { 25 | closestColor = color 26 | bestMatch = difference 27 | } 28 | } 29 | 30 | return closestColor!.1 31 | } 32 | 33 | } 34 | 35 | /// Adds the content of two dictionaries together. 36 | private func += (lhs: [UIColor: String], rhs: [UIColor: String]) -> [UIColor: String] { 37 | let summedUpDictionay = lhs.reduce(into: rhs) { (result, colorNamePair) in 38 | result[colorNamePair.key] = colorNamePair.value 39 | } 40 | return summedUpDictionay 41 | } 42 | 43 | /// A collection of lists of colors and their names. 44 | struct NamedColorList { 45 | 46 | static let BasicColors = AdditivePrimaryColors += AdditiveSecondaryColors += AdditiveTertiaryColors += GrayShadeColors 47 | 48 | static let AdditivePrimaryColors = [ 49 | UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0): NSLocalizedString("colorkit.color.name.primary.red", bundle: Bundle(for: ColorFrequency.self), comment: "red"), 50 | UIColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0): NSLocalizedString("colorkit.color.name.primary.green", bundle: Bundle(for: ColorFrequency.self), comment: "green"), 51 | UIColor(red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0): NSLocalizedString("colorkit.color.name.primary.blue", bundle: Bundle(for: ColorFrequency.self), comment: "blue") 52 | ] 53 | 54 | static let AdditiveSecondaryColors = [ 55 | UIColor(red: 1.0, green: 1.0, blue: 0.0, alpha: 1.0): NSLocalizedString("colorkit.color.name.secondary.yellow", bundle: Bundle(for: ColorFrequency.self), comment: "yellow"), 56 | UIColor(red: 1.0, green: 0.0, blue: 1.0, alpha: 1.0): NSLocalizedString("colorkit.color.name.secondary.magenta", bundle: Bundle(for: ColorFrequency.self), comment: "magenta"), 57 | UIColor(red: 0.0, green: 1.0, blue: 1.0, alpha: 1.0): NSLocalizedString("colorkit.color.name.secondary.cyan", bundle: Bundle(for: ColorFrequency.self), comment: "cyan") 58 | ] 59 | 60 | static let AdditiveTertiaryColors = [ 61 | UIColor(red: 0.0, green: 0.5, blue: 1.0, alpha: 1.0): NSLocalizedString("colorkit.color.name.tertiary.azure", bundle: Bundle(for: ColorFrequency.self), comment: "azure"), 62 | UIColor(red: 0.5, green: 0.0, blue: 1.0, alpha: 1.0): NSLocalizedString("colorkit.color.name.tertiary.violet", bundle: Bundle(for: ColorFrequency.self), comment: "violet"), 63 | UIColor(red: 1.0, green: 0.0, blue: 0.5, alpha: 1.0): NSLocalizedString("colorkit.color.name.tertiary.rose", bundle: Bundle(for: ColorFrequency.self), comment: "rose"), 64 | UIColor(red: 1.0, green: 0.5, blue: 0.0, alpha: 1.0): NSLocalizedString("colorkit.color.name.tertiary.orange", bundle: Bundle(for: ColorFrequency.self), comment: "orange"), 65 | UIColor(red: 0.5, green: 1.0, blue: 0.0, alpha: 1.0): NSLocalizedString("colorkit.color.name.tertiary.chartreuse", bundle: Bundle(for: ColorFrequency.self), comment: "chartreuse"), 66 | UIColor(red: 0.0, green: 1.0, blue: 0.5, alpha: 1.0): NSLocalizedString("colorkit.color.name.tertiary.sprint_green", bundle: Bundle(for: ColorFrequency.self), comment: "spring green") 67 | ] 68 | 69 | static let GrayShadeColors = [ 70 | UIColor.black: NSLocalizedString("colorkit.color.name.gray_shade.black", bundle: Bundle(for: ColorFrequency.self), comment: "black"), 71 | UIColor.white: NSLocalizedString("colorkit.color.name.gray_shade.white", bundle: Bundle(for: ColorFrequency.self), comment: "white"), 72 | UIColor.gray: NSLocalizedString("colorkit.color.name.gray_shade.gray", bundle: Bundle(for: ColorFrequency.self), comment: "gray") 73 | ] 74 | 75 | } 76 | -------------------------------------------------------------------------------- /ColorKit/ColorKit/RGB.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RGB.swift 3 | // ColorKit 4 | // 5 | // Created by Boris Emorine on 2/24/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct RGB { 12 | let R: CGFloat 13 | let G: CGFloat 14 | let B: CGFloat 15 | } 16 | 17 | extension UIColor { 18 | 19 | // MARK: - Pulic 20 | 21 | /// The red (R) channel of the RGB color space as a value from 0.0 to 1.0. 22 | public var red: CGFloat { 23 | CIColor(color: self).red 24 | } 25 | 26 | /// The green (G) channel of the RGB color space as a value from 0.0 to 1.0. 27 | public var green: CGFloat { 28 | CIColor(color: self).green 29 | } 30 | 31 | /// The blue (B) channel of the RGB color space as a value from 0.0 to 1.0. 32 | public var blue: CGFloat { 33 | CIColor(color: self).blue 34 | } 35 | 36 | /// The alpha (a) channel of the RGBa color space as a value from 0.0 to 1.0. 37 | public var alpha: CGFloat { 38 | CIColor(color: self).alpha 39 | } 40 | 41 | // MARK: Internal 42 | 43 | var red255: CGFloat { 44 | self.red * 255.0 45 | } 46 | 47 | var green255: CGFloat { 48 | self.green * 255.0 49 | } 50 | 51 | var blue255: CGFloat { 52 | self.blue * 255.0 53 | } 54 | 55 | var rgb: RGB { 56 | return RGB(R: self.red, G: self.green, B: self.blue) 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /ColorKit/ColorKit/Random.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Random.swift 3 | // ColorKit 4 | // 5 | // Created by Boris Emorine on 5/5/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | 13 | /// Generates a random `UIColor` instance. 14 | /// - Parameters: 15 | /// - randomizeAlpha: Whether the alpha channel should also be randomized. If set to false (default), the alpha will be set to 1.0. 16 | public static func random(randomizeAlpha: Bool = false) -> UIColor { 17 | let r = CGFloat.random(in: 0...255) / 255.0 18 | let g = CGFloat.random(in: 0...255) / 255.0 19 | let b = CGFloat.random(in: 0...255) / 255.0 20 | let a = randomizeAlpha ? 1 : CGFloat.random(in: 0...1) 21 | 22 | return UIColor(red: r, green: g, blue: b, alpha: a) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /ColorKit/ColorKit/RelativeLuminance.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RelativeLuminance.swift 3 | // ColorKit 4 | // 5 | // Created by Boris Emorine on 3/13/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | 13 | /// Computes the relative luminance of the color. 14 | /// This assume that the color is using the sRGB color space. 15 | /// This is the relative brightness, normalized where 0 is black and 1 is white. 16 | public var relativeLuminance: CGFloat { 17 | func toLinear(colorAttribute: CGFloat) -> CGFloat { 18 | if colorAttribute <= 0.03928 { 19 | return colorAttribute / 12.92 20 | } else { 21 | return pow((colorAttribute + 0.055) / 1.055, 2.4) 22 | } 23 | } 24 | 25 | let linearR = toLinear(colorAttribute: red) 26 | let linearG = toLinear(colorAttribute: green) 27 | let linearB = toLinear(colorAttribute: blue) 28 | 29 | let relativeLuminance = 0.2126 * linearR + 0.7152 * linearG + 0.0722 * linearB 30 | 31 | return relativeLuminance.rounded(.toNearestOrEven, precision: 1000) 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /ColorKit/ColorKit/XYZ.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XYZ.swift 3 | // ColorKit 4 | // 5 | // Created by Boris Emorine on 2/24/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct XYZ { 12 | let X: CGFloat 13 | let Y: CGFloat 14 | let Z: CGFloat 15 | } 16 | 17 | struct XYZCalculator { 18 | 19 | static func convert(rgb: RGB) -> XYZ { 20 | func transform(value: CGFloat) -> CGFloat { 21 | if value > 0.04045 { 22 | return pow((value + 0.055) / 1.055, 2.4) 23 | } 24 | 25 | return value / 12.92 26 | } 27 | 28 | let red = transform(value: rgb.R) * 100.0 29 | let green = transform(value: rgb.G) * 100.0 30 | let blue = transform(value: rgb.B) * 100.0 31 | 32 | let X = (red * 0.4124 + green * 0.3576 + blue * 0.1805).rounded(.toNearestOrEven, precision: 100) 33 | let Y = (red * 0.2126 + green * 0.7152 + blue * 0.0722).rounded(.toNearestOrEven, precision: 100) 34 | let Z = (red * 0.0193 + green * 0.1192 + blue * 0.9505).rounded(.toNearestOrEven, precision: 100) 35 | 36 | return XYZ(X: X, Y: Y, Z: Z) 37 | } 38 | 39 | } 40 | 41 | extension UIColor { 42 | 43 | /// The X value of the XYZ color space. 44 | public var X: CGFloat { 45 | let XYZ = XYZCalculator.convert(rgb: self.rgb) 46 | return XYZ.X 47 | } 48 | 49 | /// The Y value of the XYZ color space. 50 | public var Y: CGFloat { 51 | let XYZ = XYZCalculator.convert(rgb: self.rgb) 52 | return XYZ.Y 53 | } 54 | 55 | /// The Z value of the XYZ color space. 56 | public var Z: CGFloat { 57 | let XYZ = XYZCalculator.convert(rgb: self.rgb) 58 | return XYZ.Z 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ColorKitSampleApp 4 | // 5 | // Created by Boris Emorine on 5/31/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_1.imageset/61IWOsLc2QL._SS500_.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_1.imageset/61IWOsLc2QL._SS500_.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "61IWOsLc2QL._SS500_.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 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "cri_000000319870.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 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_2.imageset/cri_000000319870.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_2.imageset/cri_000000319870.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Hotel-California.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 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_3.imageset/Hotel-California.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_3.imageset/Hotel-California.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Never_Mind_the_Bollocks,_Here's_the_Sex_Pistols.png", 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 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_4.imageset/Never_Mind_the_Bollocks,_Here's_the_Sex_Pistols.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_4.imageset/Never_Mind_the_Bollocks,_Here's_the_Sex_Pistols.png -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Pink-Floyd-Dark-Side-of-the-Moon-album-covers-billboard-1000x1000-compressed.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 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_5.imageset/Pink-Floyd-Dark-Side-of-the-Moon-album-covers-billboard-1000x1000-compressed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_5.imageset/Pink-Floyd-Dark-Side-of-the-Moon-album-covers-billboard-1000x1000-compressed.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_6.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Tommy.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 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_6.imageset/Tommy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_6.imageset/Tommy.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_7.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "PetSoundsCover.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 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_7.imageset/PetSoundsCover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_7.imageset/PetSoundsCover.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_8.imageset/51KHXRnEG5L._SX466_.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_8.imageset/51KHXRnEG5L._SX466_.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_8.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "51KHXRnEG5L._SX466_.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 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_9.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "iconic-80s-rap-album-covers-featuring-classic-sneakers-2.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 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_9.imageset/iconic-80s-rap-album-covers-featuring-classic-sneakers-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/Album_cover_9.imageset/iconic-80s-rap-album-covers-featuring-classic-sneakers-2.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon_20pt@2x.jpg", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "icon_20pt@3x.jpg", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "icon_29pt@2x.jpg", 17 | "idiom" : "iphone", 18 | "scale" : "2x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "icon_29pt@3x.jpg", 23 | "idiom" : "iphone", 24 | "scale" : "3x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "icon_40pt@2x.jpg", 29 | "idiom" : "iphone", 30 | "scale" : "2x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "filename" : "icon_40pt@3x.jpg", 35 | "idiom" : "iphone", 36 | "scale" : "3x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "icon_60pt@2x.jpg", 41 | "idiom" : "iphone", 42 | "scale" : "2x", 43 | "size" : "60x60" 44 | }, 45 | { 46 | "filename" : "icon_60pt@3x.jpg", 47 | "idiom" : "iphone", 48 | "scale" : "3x", 49 | "size" : "60x60" 50 | }, 51 | { 52 | "idiom" : "ipad", 53 | "scale" : "1x", 54 | "size" : "20x20" 55 | }, 56 | { 57 | "idiom" : "ipad", 58 | "scale" : "2x", 59 | "size" : "20x20" 60 | }, 61 | { 62 | "idiom" : "ipad", 63 | "scale" : "1x", 64 | "size" : "29x29" 65 | }, 66 | { 67 | "idiom" : "ipad", 68 | "scale" : "2x", 69 | "size" : "29x29" 70 | }, 71 | { 72 | "idiom" : "ipad", 73 | "scale" : "1x", 74 | "size" : "40x40" 75 | }, 76 | { 77 | "idiom" : "ipad", 78 | "scale" : "2x", 79 | "size" : "40x40" 80 | }, 81 | { 82 | "idiom" : "ipad", 83 | "scale" : "1x", 84 | "size" : "76x76" 85 | }, 86 | { 87 | "idiom" : "ipad", 88 | "scale" : "2x", 89 | "size" : "76x76" 90 | }, 91 | { 92 | "idiom" : "ipad", 93 | "scale" : "2x", 94 | "size" : "83.5x83.5" 95 | }, 96 | { 97 | "idiom" : "ios-marketing", 98 | "scale" : "1x", 99 | "size" : "1024x1024" 100 | } 101 | ], 102 | "info" : { 103 | "author" : "xcode", 104 | "version" : 1 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Banner.imageset/Banner@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/Banner.imageset/Banner@2x.png -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Banner.imageset/Banner@3x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/Banner.imageset/Banner@3x.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Banner.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "Banner@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "Banner@3x.jpg", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Dominant_Color_Image_1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Dominant_Color_Image_4.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 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Dominant_Color_Image_1.imageset/Dominant_Color_Image_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/Dominant_Color_Image_1.imageset/Dominant_Color_Image_4.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Dominant_Color_Image_2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Dominant_Color_Image_2.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 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Dominant_Color_Image_2.imageset/Dominant_Color_Image_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/Dominant_Color_Image_2.imageset/Dominant_Color_Image_2.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Dominant_Color_Image_3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Dominant_Color_Image_3.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 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Dominant_Color_Image_3.imageset/Dominant_Color_Image_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/Dominant_Color_Image_3.imageset/Dominant_Color_Image_3.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Dominant_Color_Image_4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Dominant_Color_Image_1.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 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Dominant_Color_Image_4.imageset/Dominant_Color_Image_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/Dominant_Color_Image_4.imageset/Dominant_Color_Image_1.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Dominant_Color_Image_5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Dominant_Color_Image_5.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 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Dominant_Color_Image_5.imageset/Dominant_Color_Image_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/Dominant_Color_Image_5.imageset/Dominant_Color_Image_5.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Dominant_Color_Image_6.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Dominant_Color_Image_6.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 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Assets.xcassets/Dominant_Color_Image_6.imageset/Dominant_Color_Image_6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitSampleApp/Assets.xcassets/Dominant_Color_Image_6.imageset/Dominant_Color_Image_6.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 39 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 180 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/ColorDetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorDetailViewController.swift 3 | // ColorKitSampleApp 4 | // 5 | // Created by Boris Emorine on 7/17/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ColorKit 11 | 12 | class ColorDetailViewController: UITableViewController { 13 | 14 | var color: UIColor! 15 | 16 | @IBOutlet weak var colorView: UIView! 17 | @IBOutlet weak var colorNameLabel: UILabel! 18 | @IBOutlet weak var rgbLabel: UILabel! 19 | @IBOutlet weak var hexLabel: UILabel! 20 | @IBOutlet weak var cielabLabel: UILabel! 21 | @IBOutlet weak var xyzLabel: UILabel! 22 | @IBOutlet weak var cmykLabel: UILabel! 23 | @IBOutlet weak var relativeLuminanceLabel: UILabel! 24 | @IBOutlet weak var complementaryColorView: UIView! 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | 29 | colorView.backgroundColor = color 30 | colorNameLabel.text = "Name: \(color.name())" 31 | rgbLabel.text = String(format: "Red: %.2f Green: %.2f Blue: %.2f", color.red, color.green, color.blue) 32 | hexLabel.text = "Hex: \(color.hex)" 33 | cielabLabel.text = "CIE L: \(color.L) a: \(color.a) b: \(color.b)" 34 | xyzLabel.text = "X: \(color.X) Y: \(color.Y) Z:\(color.Z)" 35 | cmykLabel.text = String(format: "C: %.2f M: %.2f Y: %.2f K: %.2f", color.cyan, color.magenta, color.yellow, color.key) 36 | relativeLuminanceLabel.text = "Relative Luminance: \(color.relativeLuminance)" 37 | complementaryColorView.backgroundColor = color.complementaryColor 38 | complementaryColorView.layer.cornerRadius = 5 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/DominantColorsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DominantColorsViewController.swift 3 | // ColorKitSampleApp 4 | // 5 | // Created by Boris Emorine on 5/31/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ColorKit 11 | 12 | class ImageColorsViewController: UIViewController { 13 | 14 | @IBOutlet private weak var scrollView: UIScrollView! 15 | @IBOutlet private weak var tableView: UITableView! 16 | 17 | private var currentImage: UIImage! { 18 | didSet { 19 | guard oldValue != currentImage else { return } 20 | do { 21 | averageColor = try currentImage.averageColor() 22 | dominantColors = try currentImage.dominantColorFrequencies() 23 | } catch { 24 | fatalError(error.localizedDescription) 25 | } 26 | } 27 | } 28 | 29 | private var dominantColors = [ColorFrequency]() { 30 | didSet { 31 | tableView.reloadData() 32 | } 33 | } 34 | 35 | private var averageColor: UIColor! 36 | 37 | private static let images = [ 38 | UIImage(named: "Dominant_Color_Image_1")!, 39 | UIImage(named: "Dominant_Color_Image_2")!, 40 | UIImage(named: "Dominant_Color_Image_3")!, 41 | UIImage(named: "Dominant_Color_Image_4")!, 42 | UIImage(named: "Dominant_Color_Image_5")!, 43 | UIImage(named: "Dominant_Color_Image_6")! 44 | ] 45 | 46 | private lazy var imageViews: [UIImageView] = { 47 | return ImageColorsViewController.images.map { (image) -> UIImageView in 48 | let imageView = UIImageView(image: image) 49 | imageView.contentMode = .scaleAspectFit 50 | imageView.translatesAutoresizingMaskIntoConstraints = false 51 | return imageView 52 | } 53 | }() 54 | 55 | override func viewDidLoad() { 56 | super.viewDidLoad() 57 | 58 | tableView.dataSource = self 59 | tableView.delegate = self 60 | 61 | scrollView.delegate = self 62 | scrollView.subviews.first?.removeFromSuperview() 63 | 64 | imageViews.forEach { (imageView) in 65 | scrollView.addSubview(imageView) 66 | } 67 | 68 | currentImage = ImageColorsViewController.images.first! 69 | 70 | var constraints = [NSLayoutConstraint]() 71 | 72 | for (index, imageView) in imageViews.enumerated() { 73 | constraints += [ 74 | imageView.topAnchor.constraint(equalTo: scrollView.topAnchor), 75 | imageView.widthAnchor.constraint(equalTo: scrollView.widthAnchor), 76 | imageView.heightAnchor.constraint(equalTo: scrollView.heightAnchor), 77 | ] 78 | if index == 0 { 79 | constraints.append(imageView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor)) 80 | } else { 81 | let previousImageView = imageViews[index - 1] 82 | constraints.append(imageView.leadingAnchor.constraint(equalTo: previousImageView.trailingAnchor)) 83 | } 84 | 85 | if index == imageViews.count - 1 { 86 | constraints.append(scrollView.trailingAnchor.constraint(equalTo: imageView.trailingAnchor)) 87 | } 88 | } 89 | 90 | NSLayoutConstraint.activate(constraints) 91 | } 92 | 93 | override func viewDidLayoutSubviews() { 94 | super.viewDidLayoutSubviews() 95 | 96 | scrollView.contentSize = CGSize(width: scrollView.bounds.width * CGFloat(ImageColorsViewController.images.count), height: scrollView.bounds.height) 97 | } 98 | 99 | } 100 | 101 | extension ImageColorsViewController: UITableViewDataSource { 102 | 103 | enum DominantColorsTableViewSection: Int, CaseIterable { 104 | case dominantColor = 0 105 | case averageColor = 1 106 | } 107 | 108 | func numberOfSections(in tableView: UITableView) -> Int { 109 | return DominantColorsTableViewSection.allCases.count 110 | } 111 | 112 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 113 | guard let section = DominantColorsTableViewSection(rawValue: section) else { 114 | fatalError("Could not map section to `DominantColorsTableViewSection` enum") 115 | } 116 | 117 | switch section { 118 | case .dominantColor: 119 | return dominantColors.count 120 | case .averageColor: 121 | return 1 122 | } 123 | } 124 | 125 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 126 | guard let section = DominantColorsTableViewSection(rawValue: indexPath.section) else { 127 | fatalError("Could not map section to `DominantColorsTableViewSection` enum") 128 | } 129 | 130 | let cell = UITableViewCell() 131 | 132 | switch section { 133 | case .dominantColor: 134 | let colorFrequency = dominantColors[indexPath.row] 135 | cell.backgroundColor = colorFrequency.color 136 | cell.textLabel?.text = "\(colorFrequency.frequency)" 137 | case .averageColor: 138 | cell.backgroundColor = averageColor 139 | } 140 | return cell 141 | } 142 | 143 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 144 | guard let section = DominantColorsTableViewSection(rawValue: section) else { 145 | fatalError("Could not map section to `DominantColorsTableViewSection` enum") 146 | } 147 | 148 | switch section { 149 | case .dominantColor: 150 | return "Dominant Colors" 151 | case .averageColor: 152 | return "Average Color" 153 | } 154 | } 155 | 156 | } 157 | 158 | extension ImageColorsViewController: UITableViewDelegate { 159 | 160 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 161 | performSegue(withIdentifier: "toColorDetail", sender: nil) 162 | } 163 | 164 | } 165 | 166 | extension ImageColorsViewController: UIScrollViewDelegate { 167 | 168 | func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 169 | let numberOfPages = imageViews.count 170 | let currentPage = Int(scrollView.contentOffset.x / (scrollView.contentSize.width / CGFloat(numberOfPages))) 171 | guard let currentImage = imageViews[currentPage].image else { 172 | fatalError("Could not find image.") 173 | } 174 | 175 | self.currentImage = currentImage 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/ImageColorsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageColorsViewController.swift 3 | // ColorKitSampleApp 4 | // 5 | // Created by Boris Emorine on 5/31/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ColorKit 11 | 12 | class ImageColorsViewController: UIViewController { 13 | 14 | @IBOutlet private weak var scrollView: UIScrollView! 15 | @IBOutlet private weak var tableView: UITableView! 16 | @IBOutlet weak var pageControl: UIPageControl! 17 | 18 | private var currentImage: UIImage! { 19 | didSet { 20 | guard oldValue != currentImage else { return } 21 | do { 22 | averageColor = try currentImage.averageColor() 23 | dominantColors = try currentImage.dominantColorFrequencies() 24 | } catch { 25 | fatalError(error.localizedDescription) 26 | } 27 | } 28 | } 29 | 30 | private var dominantColors = [ColorFrequency]() { 31 | didSet { 32 | tableView.reloadData() 33 | } 34 | } 35 | 36 | private var averageColor: UIColor! 37 | 38 | private static let images = [ 39 | UIImage(named: "Dominant_Color_Image_1")!, 40 | UIImage(named: "Dominant_Color_Image_2")!, 41 | UIImage(named: "Dominant_Color_Image_3")!, 42 | UIImage(named: "Dominant_Color_Image_4")!, 43 | UIImage(named: "Dominant_Color_Image_5")!, 44 | UIImage(named: "Dominant_Color_Image_6")! 45 | ] 46 | 47 | private lazy var imageViews: [UIImageView] = { 48 | return ImageColorsViewController.images.map { (image) -> UIImageView in 49 | let imageView = UIImageView(image: image) 50 | imageView.contentMode = .scaleAspectFit 51 | imageView.translatesAutoresizingMaskIntoConstraints = false 52 | return imageView 53 | } 54 | }() 55 | 56 | override func viewDidLoad() { 57 | super.viewDidLoad() 58 | 59 | tableView.dataSource = self 60 | tableView.delegate = self 61 | 62 | scrollView.delegate = self 63 | scrollView.subviews.first?.removeFromSuperview() 64 | 65 | imageViews.forEach { (imageView) in 66 | scrollView.addSubview(imageView) 67 | } 68 | 69 | currentImage = ImageColorsViewController.images.first! 70 | 71 | var constraints = [NSLayoutConstraint]() 72 | 73 | for (index, imageView) in imageViews.enumerated() { 74 | constraints += [ 75 | imageView.topAnchor.constraint(equalTo: scrollView.topAnchor), 76 | imageView.widthAnchor.constraint(equalTo: scrollView.widthAnchor), 77 | imageView.heightAnchor.constraint(equalTo: scrollView.heightAnchor), 78 | ] 79 | if index == 0 { 80 | constraints.append(imageView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor)) 81 | } else { 82 | let previousImageView = imageViews[index - 1] 83 | constraints.append(imageView.leadingAnchor.constraint(equalTo: previousImageView.trailingAnchor)) 84 | } 85 | 86 | if index == imageViews.count - 1 { 87 | constraints.append(scrollView.trailingAnchor.constraint(equalTo: imageView.trailingAnchor)) 88 | } 89 | } 90 | 91 | NSLayoutConstraint.activate(constraints) 92 | pageControl.numberOfPages = imageViews.count 93 | } 94 | 95 | override func viewDidLayoutSubviews() { 96 | super.viewDidLayoutSubviews() 97 | 98 | scrollView.contentSize = CGSize(width: scrollView.bounds.width * CGFloat(ImageColorsViewController.images.count), height: scrollView.bounds.height) 99 | } 100 | 101 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 102 | guard let colorDetailViewController = segue.destination as? ColorDetailViewController, 103 | let selectedIndexPath = tableView.indexPathForSelectedRow, 104 | let section = DominantColorsTableViewSection(rawValue: selectedIndexPath.section) else { 105 | fatalError("Failed to get requirements for segue.") 106 | } 107 | 108 | var color: UIColor 109 | switch section { 110 | case .dominantColor: 111 | color = dominantColors[selectedIndexPath.row].color 112 | case .averageColor: 113 | color = averageColor 114 | } 115 | 116 | colorDetailViewController.color = color 117 | tableView.deselectRow(at: selectedIndexPath, animated: true) 118 | } 119 | 120 | } 121 | 122 | extension ImageColorsViewController: UITableViewDataSource { 123 | 124 | enum DominantColorsTableViewSection: Int, CaseIterable { 125 | case dominantColor = 0 126 | case averageColor = 1 127 | } 128 | 129 | func numberOfSections(in tableView: UITableView) -> Int { 130 | return DominantColorsTableViewSection.allCases.count 131 | } 132 | 133 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 134 | guard let section = DominantColorsTableViewSection(rawValue: section) else { 135 | fatalError("Could not map section to `DominantColorsTableViewSection` enum") 136 | } 137 | 138 | switch section { 139 | case .dominantColor: 140 | return dominantColors.count 141 | case .averageColor: 142 | return 1 143 | } 144 | } 145 | 146 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 147 | guard let section = DominantColorsTableViewSection(rawValue: indexPath.section) else { 148 | fatalError("Could not map section to `DominantColorsTableViewSection` enum") 149 | } 150 | 151 | let cell = UITableViewCell() 152 | 153 | switch section { 154 | case .dominantColor: 155 | let colorFrequency = dominantColors[indexPath.row] 156 | cell.backgroundColor = colorFrequency.color 157 | cell.textLabel?.text = "\(colorFrequency.frequency)" 158 | case .averageColor: 159 | cell.backgroundColor = averageColor 160 | } 161 | return cell 162 | } 163 | 164 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 165 | guard let section = DominantColorsTableViewSection(rawValue: section) else { 166 | fatalError("Could not map section to `DominantColorsTableViewSection` enum") 167 | } 168 | 169 | switch section { 170 | case .dominantColor: 171 | return "Dominant Colors" 172 | case .averageColor: 173 | return "Average Color" 174 | } 175 | } 176 | 177 | } 178 | 179 | extension ImageColorsViewController: UITableViewDelegate { 180 | 181 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 182 | performSegue(withIdentifier: "toColorDetail", sender: nil) 183 | } 184 | 185 | } 186 | 187 | extension ImageColorsViewController: UIScrollViewDelegate { 188 | 189 | func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 190 | guard !(scrollView is UITableView) else { return } 191 | let numberOfPages = imageViews.count 192 | let currentPage = Int(scrollView.contentOffset.x / (scrollView.contentSize.width / CGFloat(numberOfPages))) 193 | guard let currentImage = imageViews[currentPage].image else { 194 | fatalError("Could not find image.") 195 | } 196 | 197 | self.currentImage = currentImage 198 | pageControl.currentPage = currentPage 199 | } 200 | 201 | } 202 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIMainStoryboardFile 45 | Main 46 | UIRequiredDeviceCapabilities 47 | 48 | armv7 49 | 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | 54 | UISupportedInterfaceOrientations~ipad 55 | 56 | UIInterfaceOrientationPortrait 57 | UIInterfaceOrientationPortraitUpsideDown 58 | UIInterfaceOrientationLandscapeLeft 59 | UIInterfaceOrientationLandscapeRight 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/PaletteViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PaletteViewController.swift 3 | // ColorKitSampleApp 4 | // 5 | // Created by Boris Emorine on 8/26/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ColorKit 11 | 12 | class PaletteViewController: UIViewController { 13 | 14 | private struct Album { 15 | let image: UIImage 16 | let name: String 17 | let artist: String 18 | } 19 | 20 | @IBOutlet weak var titleLabel: UILabel! 21 | @IBOutlet weak var detailLabel: UILabel! 22 | @IBOutlet weak var imageView: UIImageView! 23 | 24 | private let albums = [ 25 | Album(image: #imageLiteral(resourceName: "Album_cover_1"), name: "My Beautiful Dark Twisted Fantasy", artist: "Kanye West"), 26 | Album(image: #imageLiteral(resourceName: "Album_cover_2"), name: "Abbey Road", artist: "The Beatles"), 27 | Album(image: #imageLiteral(resourceName: "Album_cover_3"), name: "Hotel California", artist: "Eagles"), 28 | Album(image: #imageLiteral(resourceName: "Album_cover_4"), name: "Never Mind the Bollocks", artist: "Sex Pistols"), 29 | Album(image: #imageLiteral(resourceName: "Album_cover_5"), name: "The Dark Side of the Moon", artist: "Pink Floyd"), 30 | Album(image: #imageLiteral(resourceName: "Album_cover_6"), name: "Tommy", artist: "The Who"), 31 | Album(image: #imageLiteral(resourceName: "Album_cover_7"), name: "Pet Sounds", artist: "The Beach Boys"), 32 | Album(image: #imageLiteral(resourceName: "Album_cover_8"), name: "Modern Sounds", artist: "Ray Charles"), 33 | Album(image: #imageLiteral(resourceName: "Album_cover_9"), name: "Tougher than Leather", artist: "Run DMC") 34 | ] 35 | 36 | override func viewDidLoad() { 37 | super.viewDidLoad() 38 | 39 | imageView.layer.borderWidth = 5 40 | imageView.layer.borderColor = UIColor.black.cgColor 41 | 42 | resetView() 43 | } 44 | 45 | private func resetView() { 46 | guard let album = albums.randomElement() else { 47 | fatalError("Could not get album") 48 | } 49 | 50 | imageView.image = album.image 51 | 52 | let colors = try! album.image.dominantColors() 53 | guard let palette = ColorPalette(orderedColors: colors, ignoreContrastRatio: true) else { 54 | fatalError("Could not create palette") 55 | } 56 | 57 | view.backgroundColor = palette.background 58 | 59 | titleLabel.text = album.name 60 | titleLabel.textColor = palette.primary 61 | 62 | detailLabel.text = album.artist 63 | detailLabel.textColor = palette.secondary 64 | } 65 | @IBAction func handleTapRefresh(_ sender: Any) { 66 | resetView() 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /ColorKit/ColorKitSampleApp/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // ColorKitSampleApp 4 | // 5 | // Created by Boris Emorine on 5/31/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | 16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 20 | guard let _ = (scene as? UIWindowScene) else { return } 21 | } 22 | 23 | func sceneDidDisconnect(_ scene: UIScene) { 24 | // Called as the scene is being released by the system. 25 | // This occurs shortly after the scene enters the background, or when its session is discarded. 26 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 27 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 28 | } 29 | 30 | func sceneDidBecomeActive(_ scene: UIScene) { 31 | // Called when the scene has moved from an inactive state to an active state. 32 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 33 | } 34 | 35 | func sceneWillResignActive(_ scene: UIScene) { 36 | // Called when the scene will move from an active state to an inactive state. 37 | // This may occur due to temporary interruptions (ex. an incoming phone call). 38 | } 39 | 40 | func sceneWillEnterForeground(_ scene: UIScene) { 41 | // Called as the scene transitions from the background to the foreground. 42 | // Use this method to undo the changes made on entering the background. 43 | } 44 | 45 | func sceneDidEnterBackground(_ scene: UIScene) { 46 | // Called as the scene transitions from the foreground to the background. 47 | // Use this method to save data, release shared resources, and store enough scene-specific state information 48 | // to restore the scene back to its current state. 49 | } 50 | 51 | 52 | } 53 | 54 | -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/AverageColorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AverageColorTests.swift 3 | // ColorKitTests 4 | // 5 | // Created by Boris Emorine on 5/15/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import ColorKit 11 | 12 | class AverageColorTests: XCTestCase { 13 | 14 | static let tolerance: CGFloat = 0.5 15 | 16 | /// It should compute a green average color for a green image. 17 | func testGreenImage() throws { 18 | let bundle = Bundle(for: type(of: self)) 19 | let image = UIImage(named: "Green_Square.jpg", in: bundle, compatibleWith: nil)! 20 | let averageColor = try image.averageColor() 21 | 22 | let distance = averageColor.difference(from: UIColor.green) 23 | XCTAssertLessThan(distance.associatedValue, AverageColorTests.tolerance) 24 | } 25 | 26 | /// It should compute a purple average color for a purple image. 27 | func testPurpleImage() throws { 28 | let bundle = Bundle(for: type(of: self)) 29 | let image = UIImage(named: "Purple_Square.jpg", in: bundle, compatibleWith: nil)! 30 | let averageColor = try image.averageColor() 31 | 32 | let expectedPurple = UIColor(red: 208.0 / 255.0, green: 0.0 / 255.0, blue: 255.0 / 255.0, alpha: 1.0) 33 | let distance = averageColor.difference(from: expectedPurple) 34 | XCTAssertLessThan(distance.associatedValue, AverageColorTests.tolerance) 35 | } 36 | 37 | /// It should compute a gray average color for a black & white image. 38 | func testBlackWhiteImage() throws { 39 | let bundle = Bundle(for: type(of: self)) 40 | let image = UIImage(named: "Black_White_Square.jpg", in: bundle, compatibleWith: nil)! 41 | let averageColor = try image.averageColor() 42 | 43 | let expectedGray = UIColor(red: 188.0 / 255.0, green: 188.0 / 255.0, blue: 188.0 / 255.0, alpha: 1.0) 44 | let distance = averageColor.difference(from: expectedGray) 45 | XCTAssertLessThan(distance.associatedValue, AverageColorTests.tolerance) 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/CGFloatExtensionsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGFloatExtensionsTests.swift 3 | // ColorKitTests 4 | // 5 | // Created by Boris Emorine on 2/26/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import ColorKit 11 | 12 | class CGFloatExtensionsTests: XCTestCase { 13 | 14 | func testRoundedWithPrecision10() { 15 | let sut: CGFloat = 100.39999999 16 | let roundedSut = sut.rounded(.toNearestOrEven, precision: 10) 17 | XCTAssertEqual(roundedSut, 100.4) 18 | } 19 | 20 | func testRoundedWithPrecision100() { 21 | let sut: CGFloat = 100.39999999 22 | let roundedSut = sut.rounded(.toNearestOrEven, precision: 100) 23 | XCTAssertEqual(roundedSut, 100.40) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/CGSizeExtensionsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGSizeExtensionsTests.swift 3 | // ColorKitTests 4 | // 5 | // Created by Boris Emorine on 5/30/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import ColorKit 11 | 12 | class CGSizeExtensionsTests: XCTestCase { 13 | 14 | /// A simple test with hard coded values where the target size is smaller than the original size. 15 | func testSimpleSmaller() { 16 | let originalSize = CGSize(width: 100, height: 100) 17 | let targetSize = originalSize.transformToFit(in: 100) 18 | 19 | let expectedSize = CGSize(width: 10, height: 10) 20 | XCTAssertEqual(targetSize, expectedSize) 21 | } 22 | 23 | /// A simple test with hard coded values where the target size is greater than the original size. 24 | func testSimpleGreater() { 25 | let originalSize = CGSize(width: 10, height: 10) 26 | let targetSize = originalSize.transformToFit(in: 10_000) 27 | 28 | let expectedSize = CGSize(width: 100, height: 100) 29 | XCTAssertEqual(targetSize, expectedSize) 30 | } 31 | 32 | /// It should return a target size with the expected area and keeping the same size ratio, when the target area is smaller than the original area. 33 | func testSmaller() { 34 | let originalSize = CGSize(width: 1024, height: 800) 35 | let targetArea: CGFloat = originalSize.area / CGFloat.random(in: 1...4) 36 | let targetSize = originalSize.transformToFit(in: targetArea) 37 | 38 | XCTAssertEqual(originalSize.width / originalSize.height, targetSize.width / targetSize.height, accuracy: 0.01) 39 | XCTAssertEqual(targetArea, targetSize.width * targetSize.height, accuracy: 0.01) 40 | } 41 | 42 | /// It should return a target size with the expected area and keeping the same size ratio, when the target area is greater than the original area. 43 | func testGreater() { 44 | let originalSize = CGSize(width: 1024, height: 800) 45 | let targetArea: CGFloat = originalSize.area * CGFloat.random(in: 1...4) 46 | let targetSize = originalSize.transformToFit(in: targetArea) 47 | 48 | XCTAssertEqual(originalSize.width / originalSize.height, targetSize.width / targetSize.height, accuracy: 0.01) 49 | XCTAssertEqual(targetArea, targetSize.width * targetSize.height, accuracy: 0.01) 50 | } 51 | 52 | /// It should return a target size with the expected area and keeping the same size ratio. 53 | func testRandom() { 54 | let originalSize = CGSize(width: CGFloat.random(in: 0...100000), height: CGFloat.random(in: 0...100000)) 55 | let targetArea: CGFloat = originalSize.area * CGFloat.random(in: 0...2) 56 | let targetSize = originalSize.transformToFit(in: targetArea) 57 | 58 | XCTAssertEqual(originalSize.width / originalSize.height, targetSize.width / targetSize.height, accuracy: 0.01) 59 | XCTAssertEqual(targetArea, targetSize.width * targetSize.height, accuracy: 0.01) 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/CMYKTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CMYKTests.swift 3 | // ColorKitTests 4 | // 5 | // Created by Boris Emorine on 6/20/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import ColorKit 11 | 12 | class CMYKTests: XCTestCase { 13 | 14 | func testGreen() { 15 | let color = UIColor.green 16 | 17 | XCTAssertEqual(color.cyan, 1.0) 18 | XCTAssertEqual(color.magenta, 0.0) 19 | XCTAssertEqual(color.yellow, 1.0) 20 | XCTAssertEqual(color.key, 0.0) 21 | } 22 | 23 | func testBlue() { 24 | let color = UIColor.blue 25 | 26 | XCTAssertEqual(color.cyan, 1.0) 27 | XCTAssertEqual(color.magenta, 1.0) 28 | XCTAssertEqual(color.yellow, 0.0) 29 | XCTAssertEqual(color.key, 0.0) 30 | } 31 | 32 | func testWhite() { 33 | let color = UIColor.white 34 | 35 | XCTAssertEqual(color.cyan, 0.0) 36 | XCTAssertEqual(color.magenta, 0.0) 37 | XCTAssertEqual(color.yellow, 0.0) 38 | XCTAssertEqual(color.key, 0.0) 39 | } 40 | 41 | func testArbitrary() { 42 | let color = UIColor(red: 153.0 / 255.0, green: 71.0 / 255.0, blue: 206.0 / 255.0, alpha: 1.0) 43 | 44 | XCTAssertEqual(color.cyan, 0.26, accuracy: 0.01) 45 | XCTAssertEqual(color.magenta, 0.66, accuracy: 0.01) 46 | XCTAssertEqual(color.yellow, 0.0) 47 | XCTAssertEqual(color.key, 0.19, accuracy: 0.01) 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/ColorKitTests-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/ComparisonTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ComparisonTests.swift 3 | // ColorKitTests 4 | // 5 | // Created by Boris Emorine on 2/24/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import ColorKit 11 | 12 | class ComparisonTests: XCTestCase { 13 | 14 | // MARK: - Euclidean 15 | 16 | func testWhiteWhiteEuclidean() { 17 | let color1 = UIColor.white 18 | let color2 = UIColor.white 19 | 20 | let difference = color1.difference(from: color2, using: .euclidean).associatedValue 21 | XCTAssertEqual(difference, 0) 22 | 23 | let reversedDifference = color2.difference(from: color1, using: .euclidean).associatedValue 24 | XCTAssertEqual(reversedDifference, difference) 25 | } 26 | 27 | func testPurplePurpleEuclidean() { 28 | let color1 = UIColor.purple 29 | let color2 = UIColor.purple 30 | 31 | let difference = color1.difference(from: color2, using: .euclidean).associatedValue 32 | XCTAssertEqual(difference, 0) 33 | 34 | let reversedDifference = color2.difference(from: color1, using: .euclidean).associatedValue 35 | XCTAssertEqual(reversedDifference, difference) 36 | } 37 | 38 | func testWhiteBlackEuclidean() { 39 | let color1 = UIColor.white 40 | let color2 = UIColor.black 41 | 42 | let difference = color1.difference(from: color2, using: .euclidean).associatedValue 43 | XCTAssertEqual(difference, 441.67) 44 | 45 | let reversedDifference = color2.difference(from: color1, using: .euclidean).associatedValue 46 | XCTAssertEqual(reversedDifference, difference) 47 | } 48 | 49 | func testRandomEuclidean() { 50 | let color1 = UIColor(red: 76.5 / 255.0, green: 127.5 / 255.0, blue: 178.5 / 255.0, alpha: 1.0) 51 | let color2 = UIColor(red: 127.5 / 255.0, green: 25.5 / 255.0, blue: 51.0 / 255.0, alpha: 1.0) 52 | 53 | let difference = color1.difference(from: color2, using: .euclidean).associatedValue 54 | XCTAssertEqual(difference, 171.06) 55 | 56 | let reversedDifference = color2.difference(from: color1, using: .euclidean).associatedValue 57 | XCTAssertEqual(reversedDifference, difference) 58 | } 59 | 60 | func testCloseEuclidean() { 61 | let color1 = UIColor(red: 196.0 / 255.0, green: 199.0 / 255.0, blue: 46.0 / 255.0, alpha: 1.0) 62 | let color2 = UIColor(red: 171.0 / 255.0, green: 173.0 / 255.0, blue: 50.0 / 255.0, alpha: 1.0) 63 | 64 | let difference = color1.difference(from: color2, using: .euclidean).associatedValue 65 | XCTAssertEqual(difference, 36.29) 66 | 67 | let reversedDifference = color2.difference(from: color1, using: .euclidean).associatedValue 68 | XCTAssertEqual(reversedDifference, difference) 69 | } 70 | 71 | // MARK: - CIE76 72 | 73 | func testWhiteWhiteCIE76() { 74 | let color1 = UIColor.white 75 | let color2 = UIColor.white 76 | 77 | let difference = color1.difference(from: color2, using: .CIE76).associatedValue 78 | XCTAssertEqual(difference, 0) 79 | 80 | let reversedDifference = color2.difference(from: color1, using: .CIE76).associatedValue 81 | XCTAssertEqual(reversedDifference, difference) 82 | } 83 | 84 | func testPurplePurpleCIE76() { 85 | let color1 = UIColor.purple 86 | let color2 = UIColor.purple 87 | 88 | let difference = color1.difference(from: color2, using: .CIE76).associatedValue 89 | XCTAssertEqual(difference, 0) 90 | 91 | let reversedDifference = color2.difference(from: color1, using: .CIE76).associatedValue 92 | XCTAssertEqual(reversedDifference, difference) 93 | } 94 | 95 | func testWhiteBlackCIE76() { 96 | let color1 = UIColor.white 97 | let color2 = UIColor.black 98 | 99 | let difference = color1.difference(from: color2, using: .CIE76).associatedValue 100 | XCTAssertEqual(difference, 100.0) 101 | 102 | let reversedDifference = color2.difference(from: color1, using: .CIE76).associatedValue 103 | XCTAssertEqual(reversedDifference, difference) 104 | } 105 | 106 | func testRandomCIE76() { 107 | let color1 = UIColor(red: 76.5 / 255.0, green: 127.5 / 255.0, blue: 178.5 / 255.0, alpha: 1.0) 108 | let color2 = UIColor(red: 127.5 / 255.0, green: 25.5 / 255.0, blue: 51.0 / 255.0, alpha: 1.0) 109 | 110 | let difference = color1.difference(from: color2, using: .CIE76).associatedValue 111 | XCTAssertEqual(difference, 67.55) 112 | 113 | let reversedDifference = color2.difference(from: color1, using: .CIE76).associatedValue 114 | XCTAssertEqual(reversedDifference, difference) 115 | } 116 | 117 | func testCloseCIE76() { 118 | let color1 = UIColor(red: 196.0 / 255.0, green: 199.0 / 255.0, blue: 46.0 / 255.0, alpha: 1.0) 119 | let color2 = UIColor(red: 171.0 / 255.0, green: 173.0 / 255.0, blue: 50.0 / 255.0, alpha: 1.0) 120 | 121 | let difference = color1.difference(from: color2, using: .CIE76).associatedValue 122 | XCTAssertEqual(difference, 14.25) 123 | 124 | let reversedDifference = color2.difference(from: color1, using: .CIE76).associatedValue 125 | XCTAssertEqual(reversedDifference, difference) 126 | } 127 | 128 | // MARK: - CIE94 129 | 130 | func testWhiteWhiteCIE94() { 131 | let color1 = UIColor.white 132 | let color2 = UIColor.white 133 | 134 | let difference = color1.difference(from: color2, using: .CIE94).associatedValue 135 | XCTAssertEqual(difference, 0) 136 | 137 | let reversedDifference = color2.difference(from: color1, using: .CIE94).associatedValue 138 | XCTAssertEqual(reversedDifference, difference) 139 | } 140 | 141 | func testPurplePurpleCIE94() { 142 | let color1 = UIColor.purple 143 | let color2 = UIColor.purple 144 | 145 | let difference = color1.difference(from: color2, using: .CIE94).associatedValue 146 | XCTAssertEqual(difference, 0) 147 | 148 | let reversedDifference = color2.difference(from: color1, using: .CIE94).associatedValue 149 | XCTAssertEqual(reversedDifference, difference) 150 | } 151 | 152 | func testWhiteBlackCIE94() { 153 | let color1 = UIColor.white 154 | let color2 = UIColor.black 155 | 156 | let difference = color1.difference(from: color2, using: .CIE94).associatedValue 157 | XCTAssertEqual(difference, 100.0) 158 | 159 | let reversedDifference = color2.difference(from: color1, using: .CIE94).associatedValue 160 | XCTAssertEqual(reversedDifference, difference) 161 | } 162 | 163 | func testRandomCIE94() { 164 | let color1 = UIColor(red: 76.5 / 255.0, green: 127.5 / 255.0, blue: 178.5 / 255.0, alpha: 1.0) 165 | let color2 = UIColor(red: 127.5 / 255.0, green: 25.5 / 255.0, blue: 51.0 / 255.0, alpha: 1.0) 166 | 167 | let difference = color1.difference(from: color2, using: .CIE94).associatedValue 168 | XCTAssertEqual(difference, 48.31) 169 | 170 | let reversedDifference = color2.difference(from: color1, using: .CIE94).associatedValue 171 | XCTAssertEqual(reversedDifference, 43.99) 172 | } 173 | 174 | func testCloseCIE94() { 175 | let color1 = UIColor(red: 196.0 / 255.0, green: 199.0 / 255.0, blue: 46.0 / 255.0, alpha: 1.0) 176 | let color2 = UIColor(red: 171.0 / 255.0, green: 173.0 / 255.0, blue: 50.0 / 255.0, alpha: 1.0) 177 | 178 | let difference = color1.difference(from: color2, using: .CIE94).associatedValue 179 | XCTAssertEqual(difference, 9.5) 180 | 181 | let reversedDifference = color2.difference(from: color1, using: .CIE94).associatedValue 182 | XCTAssertEqual(reversedDifference, 9.6) 183 | } 184 | 185 | 186 | } 187 | -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/ComplementaryColorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ComplementaryColorTests.swift 3 | // ColorKitTests 4 | // 5 | // Created by Boris Emorine on 3/18/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import ColorKit 11 | 12 | class ComplementaryColorTests: XCTestCase { 13 | 14 | func testBlack() { 15 | let black = UIColor.black 16 | let complementaryColor = black.complementaryColor 17 | XCTAssertEqual(complementaryColor, UIColor(red: 1, green: 1, blue: 1, alpha: 1.0)) 18 | } 19 | 20 | func testWhite() { 21 | let white = UIColor.white 22 | let complementaryColor = white.complementaryColor 23 | XCTAssertEqual(complementaryColor, UIColor(red: 0, green: 0, blue: 0, alpha: 1.0)) 24 | } 25 | 26 | func testBlue() { 27 | let blue = UIColor.blue 28 | let complementaryColor = blue.complementaryColor 29 | XCTAssertEqual(complementaryColor, UIColor(red: 255.0 / 255.0, green: 255.0 / 255.0, blue: 0, alpha: 1.0)) 30 | } 31 | 32 | func testYellow() { 33 | let yellow = UIColor(red: 255.0 / 255.0, green: 255.0 / 255.0, blue: 0, alpha: 1.0) 34 | let complementaryColor = yellow.complementaryColor 35 | XCTAssertEqual(complementaryColor, UIColor.blue) 36 | } 37 | 38 | func testRed() { 39 | let red = UIColor.red 40 | let complementaryColor = red.complementaryColor 41 | XCTAssertEqual(complementaryColor, UIColor(red: 0.0 / 255.0, green: 255.0 / 255.0, blue: 255.0 / 255.0, alpha: 1.0)) 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/ContrastRatioTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContrastRatioTests.swift 3 | // ColorKitTests 4 | // 5 | // Created by Boris Emorine on 3/13/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import ColorKit 11 | 12 | class ContrastRatioTests: XCTestCase { 13 | 14 | func testBlackWhite() { 15 | let color = UIColor.white 16 | let backgroundColor = UIColor.black 17 | let contrastRatioResult = color.contrastRatio(with: backgroundColor) 18 | XCTAssertEqual(contrastRatioResult.associatedValue, 21.0) 19 | } 20 | 21 | func testWhiteBlack() { 22 | let color = UIColor.black 23 | let backgroundColor = UIColor.white 24 | let contrastRatioResult = color.contrastRatio(with: backgroundColor) 25 | XCTAssertEqual(contrastRatioResult.associatedValue, 21.0) 26 | } 27 | 28 | func testOrangeOrangeClose() { 29 | let color = UIColor(red: 243.0 / 255.0, green: 120.0 / 255.0, blue: 9.0 / 255.0, alpha: 1.0) 30 | let backgroundColor = UIColor(red: 222.0 / 255.0, green: 100.0 / 255.0, blue: 10.0 / 255.0, alpha: 1.0) 31 | let contrastRatioResult = color.contrastRatio(with: backgroundColor) 32 | XCTAssertEqual(contrastRatioResult.associatedValue, 1.26) 33 | } 34 | 35 | func testOrangeOrange() { 36 | let color = UIColor(red: 243.0 / 255.0, green: 120.0 / 255.0, blue: 9.0 / 255.0, alpha: 1.0) 37 | let backgroundColor = UIColor(red: 243.0 / 255.0, green: 120.0 / 255.0, blue: 9.0 / 255.0, alpha: 1.0) 38 | let contrastRatioResult = color.contrastRatio(with: backgroundColor) 39 | XCTAssertEqual(contrastRatioResult.associatedValue, 1.0) 40 | } 41 | 42 | func testGreenPurple() { 43 | let green = UIColor(red: 0.0 / 255.0, green: 255.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0) 44 | let blue = UIColor(red: 0.0 / 255.0, green: 0.0 / 255.0, blue: 255.0 / 255.0, alpha: 1.0) 45 | let contrastRatioResult = green.contrastRatio(with: blue) 46 | XCTAssertEqual(contrastRatioResult.associatedValue, 6.27) 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/DominantColorQualityTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DominantColorQualityTests.swift 3 | // ColorKitTests 4 | // 5 | // Created by Boris Emorine on 5/30/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import ColorKit 11 | 12 | class DominantColorQualityTests: XCTestCase { 13 | 14 | /// It should return the exact same size (original size) if the quality is set to best. 15 | func testBestQuality() { 16 | let quality = UIImage.DominantColorQuality.best 17 | let originalSize = CGSize(width: CGFloat.random(in: 0...10000), height: CGFloat.random(in: 0...10000)) 18 | let targetSize = quality.targetSize(for: originalSize) 19 | 20 | XCTAssertEqual(targetSize, originalSize) 21 | } 22 | 23 | /// It should return the exact same size (original size) if the original size is smaller than the size we're trying to reach. 24 | func testLowerArea() { 25 | let quality = UIImage.DominantColorQuality.fair 26 | let originalSize = CGSize(width: 1, height: 1) 27 | let targetSize = quality.targetSize(for: originalSize) 28 | 29 | XCTAssertEqual(targetSize, originalSize) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/DominantColorsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DominantColorsTests.swift 3 | // ColorKitTests 4 | // 5 | // Created by Boris Emorine on 5/19/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import ColorKit 11 | 12 | class DominantColorsTests: XCTestCase { 13 | 14 | func testGreenImage() throws { 15 | let bundle = Bundle(for: type(of: self)) 16 | let image = UIImage(named: "Green_Square.jpg", in: bundle, compatibleWith: nil)! 17 | let dominantColors = try image.dominantColorFrequencies(with: .best) 18 | 19 | XCTAssertEqual(dominantColors.count, 1) 20 | guard let distance = dominantColors.first?.color.difference(from: UIColor.green) else { 21 | XCTFail("Could not get distance from dominant color.") 22 | return 23 | } 24 | XCTAssertLessThan(distance.associatedValue, AverageColorTests.tolerance) 25 | XCTAssertEqual(dominantColors.first?.frequency, 1.0) 26 | } 27 | 28 | func testBlackWhiteImage() throws { 29 | let bundle = Bundle(for: type(of: self)) 30 | let image = UIImage(named: "Black_White_Square.jpg", in: bundle, compatibleWith: nil)! 31 | let colorFrequencies = try image.dominantColorFrequencies(with: .best) 32 | let dominantColors = colorFrequencies.map({ $0.color }) 33 | 34 | XCTAssertEqual(dominantColors.count, 2) 35 | XCTAssertTrue(dominantColors.contains(UIColor(red: 0, green: 0, blue: 0, alpha: 1))) 36 | XCTAssertTrue(dominantColors.contains(UIColor(red: 1, green: 1, blue: 1, alpha: 1))) 37 | verifySorted(colorsFrequencies: colorFrequencies) 38 | 39 | XCTAssertEqual(colorFrequencies.first?.frequency, 0.5) 40 | XCTAssertEqual(colorFrequencies[1].frequency, 0.5) 41 | } 42 | 43 | func testRedBlueGreenImage() throws { 44 | let bundle = Bundle(for: type(of: self)) 45 | let image = UIImage(named: "Red_Green_Blue.png", in: bundle, compatibleWith: nil)! 46 | let colorFrequencies = try image.dominantColorFrequencies(with: .best) 47 | let dominantColors = colorFrequencies.map({ $0.color }) 48 | 49 | XCTAssertEqual(dominantColors.count, 3) 50 | XCTAssertTrue(dominantColors.contains(UIColor(red: 1, green: 0, blue: 0, alpha: 1))) 51 | XCTAssertTrue(dominantColors.contains(UIColor(red: 0, green: 1, blue: 0, alpha: 1))) 52 | XCTAssertTrue(dominantColors.contains(UIColor(red: 0, green: 0, blue: 1, alpha: 1))) 53 | verifySorted(colorsFrequencies: colorFrequencies) 54 | } 55 | 56 | func testRedBlueGreenBlack() throws { 57 | let bundle = Bundle(for: type(of: self)) 58 | let image = UIImage(named: "Red_Green_Blue_Black_Mini.png", in: bundle, compatibleWith: nil)! 59 | let colorFrequencies = try image.dominantColorFrequencies(with: .best) 60 | let dominantColors = colorFrequencies.map({ $0.color }) 61 | 62 | XCTAssertEqual(dominantColors.count, 4) 63 | XCTAssertTrue(dominantColors.contains(UIColor(red: 0, green: 0, blue: 0, alpha: 1))) 64 | XCTAssertTrue(dominantColors.contains(UIColor(red: 1, green: 0, blue: 0, alpha: 1))) 65 | XCTAssertTrue(dominantColors.contains(UIColor(red: 0, green: 1, blue: 0, alpha: 1))) 66 | XCTAssertTrue(dominantColors.contains(UIColor(red: 0, green: 0, blue: 1, alpha: 1))) 67 | verifySorted(colorsFrequencies: colorFrequencies) 68 | } 69 | 70 | func testRedBlueGreenRandom() throws { 71 | let bundle = Bundle(for: type(of: self)) 72 | let image = UIImage(named: "Red_Green_Blue_Random_Mini.png", in: bundle, compatibleWith: nil)! 73 | let colorFrequencies = try image.dominantColorFrequencies(with: .best) 74 | let dominantColors = colorFrequencies.map({ $0.color }) 75 | 76 | XCTAssertTrue(dominantColors.contains(UIColor(red: 1, green: 0, blue: 0, alpha: 1))) 77 | XCTAssertTrue(dominantColors.contains(UIColor(red: 0, green: 1, blue: 0, alpha: 1))) 78 | XCTAssertTrue(dominantColors.contains(UIColor(red: 0, green: 0, blue: 1, alpha: 1))) 79 | verifySorted(colorsFrequencies: colorFrequencies) 80 | } 81 | 82 | func verifySorted(colorsFrequencies: [ColorFrequency]) { 83 | var previousCount: CGFloat? 84 | 85 | colorsFrequencies.forEach { (colorFrequency) in 86 | guard let oldCount = previousCount else { 87 | previousCount = colorFrequency.frequency 88 | return 89 | } 90 | 91 | if oldCount < colorFrequency.frequency { 92 | XCTFail("The order of the color frenquecy is not correct.") 93 | } 94 | 95 | previousCount = colorFrequency.frequency 96 | } 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/HexTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HexTests.swift 3 | // ColorKitTests 4 | // 5 | // Created by Boris Emorine on 2/27/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import ColorKit 11 | 12 | class HexTests: XCTestCase { 13 | 14 | private let blackHex = "#000000" 15 | private let whiteHex = "#ffffff" 16 | private let redHex = "#ff0000" 17 | private let darkGreen = "#32a852" 18 | private let lightGreen = "#43ff64d9" 19 | 20 | // Init 21 | 22 | func testInitBlack() { 23 | let color = UIColor(hex: blackHex)! 24 | XCTAssertEqual(color.red, 0) 25 | XCTAssertEqual(color.green, 0) 26 | XCTAssertEqual(color.blue, 0) 27 | XCTAssertEqual(color.alpha, 1) 28 | } 29 | 30 | func testInitWhite() { 31 | let color = UIColor(hex: whiteHex)! 32 | XCTAssertEqual(color.red, 1) 33 | XCTAssertEqual(color.green, 1) 34 | XCTAssertEqual(color.blue, 1) 35 | XCTAssertEqual(color.alpha, 1) 36 | } 37 | 38 | func testInitRed() { 39 | let color = UIColor(hex: redHex)! 40 | XCTAssertEqual(color.red, 255.0 / 255.0) 41 | XCTAssertEqual(color.green, 0.0 / 255.0) 42 | XCTAssertEqual(color.blue, 0.0 / 255.0) 43 | XCTAssertEqual(color.alpha, 1) 44 | } 45 | 46 | func testInitDarkGreen() { 47 | let color = UIColor(hex: darkGreen)! 48 | XCTAssertEqual(color.red, 50.0 / 255.0) 49 | XCTAssertEqual(color.green, 168.0 / 255.0) 50 | XCTAssertEqual(color.blue, 82.0 / 255.0) 51 | XCTAssertEqual(color.alpha, 1) 52 | } 53 | 54 | func testInitLightGreen() { 55 | let color = UIColor(hex: lightGreen)! 56 | XCTAssertEqual(color.red, 67.0 / 255.0) 57 | XCTAssertEqual(color.green, 255.0 / 255.0) 58 | XCTAssertEqual(color.blue, 100.0 / 255.0) 59 | XCTAssertEqual(color.alpha, 0.85, accuracy: 0.001) 60 | } 61 | 62 | // hex 63 | 64 | func testHexBlack() { 65 | let color = UIColor.black 66 | XCTAssertEqual(color.hex, blackHex) 67 | } 68 | 69 | func testHexWhite() { 70 | let color = UIColor.white 71 | XCTAssertEqual(color.hex, whiteHex) 72 | } 73 | 74 | func testHexRed() { 75 | let color = UIColor.red 76 | XCTAssertEqual(color.hex, redHex) 77 | } 78 | 79 | func testHexDarkGreen() { 80 | let color = UIColor(red: 50.0 / 255.0, green: 168.0 / 255.0, blue: 82.0 / 255.0, alpha: 1.0) 81 | XCTAssertEqual(color.hex, darkGreen) 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/LabTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LabTests.swift 3 | // ColorKitTests 4 | // 5 | // Created by Boris Emorine on 2/26/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class LabTests: XCTestCase { 12 | 13 | func testGreen() { 14 | let color = UIColor.green 15 | 16 | XCTAssertEqual(color.L, 87.74) 17 | XCTAssertEqual(color.a, -86.18) 18 | XCTAssertEqual(color.b, 83.18) 19 | } 20 | 21 | func testWhite() { 22 | let color = UIColor.white 23 | 24 | XCTAssertEqual(color.L, 100.0) 25 | XCTAssertEqual(color.a, 0.01) 26 | XCTAssertEqual(color.b, -0.01) 27 | } 28 | 29 | func testArbitrary() { 30 | let color = UIColor(red: 129.0 / 255.0, green: 200.0 / 255.0, blue: 10.0 / 255.0, alpha: 1.0) 31 | 32 | XCTAssertEqual(color.L, 73.55) 33 | XCTAssertEqual(color.a, -46.45) 34 | XCTAssertEqual(color.b, 72.04) 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/NameTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NameTests.swift 3 | // ColorKitTests 4 | // 5 | // Created by Boris Emorine on 12/9/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import ColorKit 11 | 12 | class NameTests: XCTestCase { 13 | 14 | func testPrimaryExact() { 15 | let color = UIColor.blue 16 | XCTAssertEqual(color.name(), "blue") 17 | } 18 | 19 | func testSecondaryExact() { 20 | let color = UIColor.purple 21 | XCTAssertEqual(color.name(), "violet") 22 | } 23 | 24 | func testTertiaryExact() { 25 | let color = UIColor(red: 0.5, green: 1.0, blue: 0.0, alpha: 1.0) 26 | XCTAssertEqual(color.name(), "chartreuse") 27 | } 28 | 29 | func testClose() { 30 | let color = UIColor(red: 0.9, green: 0.0, blue: 0.0, alpha: 1.0) 31 | XCTAssertEqual(color.name(), "red") 32 | } 33 | 34 | func testBlack() { 35 | let color = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0) 36 | XCTAssertEqual(color.name(), "black") 37 | } 38 | 39 | func testWhite() { 40 | let color = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0) 41 | XCTAssertEqual(color.name(), "white") 42 | } 43 | 44 | func testGray() { 45 | let color = UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 1.0) 46 | XCTAssertEqual(color.name(), "gray") 47 | } 48 | 49 | func testDarkGray() { 50 | let color = UIColor(red: 0.3, green: 0.3, blue: 0.3, alpha: 1.0) 51 | XCTAssertEqual(color.name(), "gray") 52 | } 53 | 54 | func testLightGray() { 55 | let color = UIColor(red: 0.7, green: 0.7, blue: 0.7, alpha: 1.0) 56 | XCTAssertEqual(color.name(), "gray") 57 | } 58 | 59 | func testRandom() { 60 | let color = UIColor.random() 61 | XCTAssertNotNil(color.name()) 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/PaletteTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PaletteTests.swift 3 | // ColorKitTests 4 | // 5 | // Created by Boris Emorine on 7/6/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import ColorKit 11 | 12 | class PaletteTests: XCTestCase { 13 | 14 | // MARK: - Colors 15 | 16 | func testPaletteNoColors() { 17 | XCTAssertNil(ColorPalette(colors: [])) 18 | } 19 | 20 | func testPaletteOneColor() { 21 | XCTAssertNil(ColorPalette(colors: [.green])) 22 | } 23 | 24 | func testPaletteSameColors() { 25 | XCTAssertNil(ColorPalette(colors: [.green, .green, .green, .green])) 26 | } 27 | 28 | func testPaletteBlackWhiteColors() { 29 | let colorPalette = ColorPalette(colors: [.black, .white]) 30 | XCTAssertEqual(colorPalette?.background, .black) 31 | XCTAssertEqual(colorPalette?.primary, .white) 32 | XCTAssertNil(colorPalette?.secondary) 33 | } 34 | 35 | func testPaletteBlackWhiteColorsBright() { 36 | let colorPalette = ColorPalette(colors: [.black, .white], darkBackground: false) 37 | XCTAssertEqual(colorPalette?.background, .white) 38 | XCTAssertEqual(colorPalette?.primary, .black) 39 | XCTAssertNil(colorPalette?.secondary) 40 | } 41 | 42 | func testCloseColors() { 43 | XCTAssertNil(ColorPalette(colors: [.blue, UIColor(red: 0, green: 0, blue: 0.8, alpha: 1.0)])) 44 | } 45 | 46 | func testRealUseCase() { 47 | let darkBlue = UIColor(red: 0.0 / 255.0, green: 120.0 / 255.0, blue: 190.0 / 255.0, alpha: 1.0) 48 | let brightBlue = UIColor(red: 110.0 / 255.0, green: 178.0 / 255.0, blue: 200.0 / 255.0, alpha: 1.0) 49 | let orange = UIColor(red: 203.0 / 255.0, green: 179.0 / 255.0, blue: 121.0 / 255.0, alpha: 1.0) 50 | let colorPalette = ColorPalette(colors: [darkBlue, brightBlue, orange], ignoreContrastRatio: true) 51 | XCTAssertEqual(colorPalette?.background, darkBlue) 52 | XCTAssertEqual(colorPalette?.primary, orange) 53 | XCTAssertEqual(colorPalette?.secondary, brightBlue) 54 | } 55 | 56 | func testRealUseCase2() { 57 | let red = UIColor(red: 255.0 / 255.0, green: 21.0 / 255.0, blue: 13.0 / 255.0, alpha: 1.0) 58 | let darkBlue = UIColor(red: 76.0 / 255.0, green: 101.0 / 255.0, blue: 122.0 / 255.0, alpha: 1.0) 59 | let white = UIColor.white 60 | let colorPalette = ColorPalette(colors: [red, darkBlue, white], darkBackground: false) 61 | XCTAssertEqual(colorPalette?.background, white) 62 | XCTAssertEqual(colorPalette?.primary, darkBlue) 63 | XCTAssertEqual(colorPalette?.secondary, red) 64 | } 65 | 66 | // MARK: - Ordered Colors 67 | 68 | func testPaletteNoOrderedColors() { 69 | XCTAssertNil(ColorPalette(orderedColors: [])) 70 | } 71 | 72 | func testPaletteOneOrderedColor() { 73 | XCTAssertNil(ColorPalette(orderedColors: [.green])) 74 | } 75 | 76 | func testPaletteSameOrderedColors() { 77 | XCTAssertNil(ColorPalette(orderedColors: [.green, .green, .green, .green])) 78 | } 79 | 80 | func testPaletteBlackWhiteOrderedColors() { 81 | let colorPalette = ColorPalette(orderedColors: [.black, .white]) 82 | XCTAssertEqual(colorPalette?.background, .black) 83 | XCTAssertEqual(colorPalette?.primary, .white) 84 | XCTAssertNil(colorPalette?.secondary) 85 | } 86 | 87 | func testPaletteWhiteBlackOrderedColorsBright() { 88 | let colorPalette = ColorPalette(orderedColors: [.white, .black], darkBackground: false) 89 | XCTAssertEqual(colorPalette?.background, .white) 90 | XCTAssertEqual(colorPalette?.primary, .black) 91 | XCTAssertNil(colorPalette?.secondary) 92 | } 93 | 94 | func testPaletteBlackWhiteOrderedColorsBright() { 95 | let colorPalette = ColorPalette(orderedColors: [.black, .white], darkBackground: false) 96 | XCTAssertEqual(colorPalette?.background, .black) 97 | XCTAssertEqual(colorPalette?.primary, .white) 98 | XCTAssertNil(colorPalette?.secondary) 99 | } 100 | 101 | func testCloseOrderedColors() { 102 | XCTAssertNil(ColorPalette(orderedColors: [.blue, UIColor(red: 0, green: 0, blue: 0.8, alpha: 1.0)])) 103 | } 104 | 105 | func testRealUseCaseOrdered() { 106 | let darkBlue = UIColor(red: 0.0 / 255.0, green: 120.0 / 255.0, blue: 190.0 / 255.0, alpha: 1.0) 107 | let brightBlue = UIColor(red: 110.0 / 255.0, green: 178.0 / 255.0, blue: 200.0 / 255.0, alpha: 1.0) 108 | let orange = UIColor(red: 203.0 / 255.0, green: 179.0 / 255.0, blue: 121.0 / 255.0, alpha: 1.0) 109 | let colorPalette = ColorPalette(orderedColors: [darkBlue, brightBlue, orange], ignoreContrastRatio: true) 110 | XCTAssertEqual(colorPalette?.background, darkBlue) 111 | XCTAssertEqual(colorPalette?.primary, brightBlue) 112 | XCTAssertEqual(colorPalette?.secondary, orange) 113 | } 114 | 115 | func testRealUseCase2Ordered() { 116 | let red = UIColor(red: 255.0 / 255.0, green: 21.0 / 255.0, blue: 13.0 / 255.0, alpha: 1.0) 117 | let darkBlue = UIColor(red: 76.0 / 255.0, green: 101.0 / 255.0, blue: 122.0 / 255.0, alpha: 1.0) 118 | let white = UIColor.white 119 | let colorPalette = ColorPalette(orderedColors: [red, darkBlue, white], darkBackground: false) 120 | XCTAssertEqual(colorPalette?.background, red) 121 | XCTAssertEqual(colorPalette?.primary, white) 122 | XCTAssertNil(colorPalette?.secondary) 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/RGBTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RGBTests.swift 3 | // ColorKitTests 4 | // 5 | // Created by Boris Emorine on 2/24/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import ColorKit 11 | 12 | class RGBTests: XCTestCase { 13 | 14 | func testRed() { 15 | let red = UIColor.red 16 | XCTAssertEqual(red.red, 1.0) 17 | XCTAssertEqual(red.green, 0.0) 18 | XCTAssertEqual(red.blue, 0.0) 19 | XCTAssertEqual(red.alpha, 1.0) 20 | } 21 | 22 | func testGreen() { 23 | let green = UIColor.green 24 | XCTAssertEqual(green.red, 0.0) 25 | XCTAssertEqual(green.green, 1.0) 26 | XCTAssertEqual(green.blue, 0.0) 27 | XCTAssertEqual(green.alpha, 1.0) 28 | } 29 | 30 | func testBlue() { 31 | let blue = UIColor.blue 32 | XCTAssertEqual(blue.red, 0.0) 33 | XCTAssertEqual(blue.green, 0.0) 34 | XCTAssertEqual(blue.blue, 1.0) 35 | XCTAssertEqual(blue.alpha, 1.0) 36 | } 37 | 38 | func testWhite() { 39 | let blue = UIColor.white 40 | XCTAssertEqual(blue.red, 1.0) 41 | XCTAssertEqual(blue.green, 1.0) 42 | XCTAssertEqual(blue.blue, 1.0) 43 | XCTAssertEqual(blue.alpha, 1.0) 44 | } 45 | 46 | func testBlack() { 47 | let blue = UIColor.black 48 | XCTAssertEqual(blue.red, 0.0) 49 | XCTAssertEqual(blue.green, 0.0) 50 | XCTAssertEqual(blue.blue, 0.0) 51 | XCTAssertEqual(blue.alpha, 1.0) 52 | } 53 | 54 | func testGray() { 55 | let blue = UIColor.gray 56 | XCTAssertEqual(blue.red, 0.5) 57 | XCTAssertEqual(blue.green, 0.5) 58 | XCTAssertEqual(blue.blue, 0.5) 59 | XCTAssertEqual(blue.alpha, 1.0) 60 | } 61 | 62 | func testPurple() { 63 | let blue = UIColor.purple 64 | XCTAssertEqual(blue.red, 0.5) 65 | XCTAssertEqual(blue.green, 0.0) 66 | XCTAssertEqual(blue.blue, 0.5) 67 | XCTAssertEqual(blue.alpha, 1.0) 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/RelativeLuminanceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RelativeLuminanceTests.swift 3 | // ColorKitTests 4 | // 5 | // Created by Boris Emorine on 3/13/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import ColorKit 11 | 12 | class RelativeLuminanceTests: XCTestCase { 13 | 14 | func testWhite() { 15 | let color = UIColor.white 16 | XCTAssertEqual(color.relativeLuminance, 1.0) 17 | } 18 | 19 | func testBlack() { 20 | let color = UIColor.black 21 | XCTAssertEqual(color.relativeLuminance, 0.0) 22 | } 23 | 24 | func testOrange() { 25 | let color = UIColor(red: 98.0 / 255.0, green: 44.0 / 255.0, blue: 8.0 / 255.0, alpha: 1.0) 26 | XCTAssertEqual(color.relativeLuminance, 0.044) 27 | } 28 | 29 | func testPurple() { 30 | let color = UIColor(red: 120 / 255.0, green: 90.0 / 255.0, blue: 200.0 / 255.0, alpha: 1.0) 31 | XCTAssertEqual(color.relativeLuminance, 0.155) 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/Resources/Black_White_Square.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitTests/Resources/Black_White_Square.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/Resources/Blue_Green_Square.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitTests/Resources/Blue_Green_Square.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/Resources/Blue_Square_1x1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitTests/Resources/Blue_Square_1x1.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/Resources/Green_Square.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitTests/Resources/Green_Square.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/Resources/Purple_Square.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitTests/Resources/Purple_Square.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/Resources/Red_Green_Blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitTests/Resources/Red_Green_Blue.png -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/Resources/Red_Green_Blue_Black_Mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitTests/Resources/Red_Green_Blue_Black_Mini.png -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/Resources/Red_Green_Blue_Random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitTests/Resources/Red_Green_Blue_Random.png -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/Resources/Red_Green_Blue_Random_Mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitTests/Resources/Red_Green_Blue_Random_Mini.png -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/Resources/Test_Image_1.Jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitTests/Resources/Test_Image_1.Jpeg -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/Resources/Test_Image_2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitTests/Resources/Test_Image_2.jpeg -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/Resources/Test_Image_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boris-Em/ColorKit/6e4375a6126eac0a29893c607ae318bdbe33310c/ColorKit/ColorKitTests/Resources/Test_Image_3.jpg -------------------------------------------------------------------------------- /ColorKit/ColorKitTests/XYZTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XYZTests.swift 3 | // ColorKitTests 4 | // 5 | // Created by Boris Emorine on 2/24/20. 6 | // Copyright © 2020 BorisEmorine. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import ColorKit 11 | 12 | class XYZTests: XCTestCase { 13 | 14 | func testGreen() { 15 | let color = UIColor.green 16 | 17 | XCTAssertEqual(color.X, 35.76) 18 | XCTAssertEqual(color.Y, 71.52) 19 | XCTAssertEqual(color.Z, 11.92) 20 | } 21 | 22 | func testWhite() { 23 | let color = UIColor.white 24 | 25 | XCTAssertEqual(color.X, 95.05) 26 | XCTAssertEqual(color.Y, 100.0) 27 | XCTAssertEqual(color.Z, 108.9) 28 | } 29 | 30 | func testArbitrary() { 31 | let color = UIColor(red: 129.0 / 255.0, green: 200.0 / 255.0, blue: 10.0 / 255.0, alpha: 1.0) 32 | 33 | XCTAssertEqual(color.X, 29.76) 34 | XCTAssertEqual(color.Y, 46.0) 35 | XCTAssertEqual(color.Z, 7.6) 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Boris Emorine 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "ColorKit", 8 | platforms: [ 9 | .iOS(.v13), 10 | ], 11 | products: [ 12 | .library( 13 | name: "ColorKit", 14 | targets: ["ColorKit"]), 15 | ], 16 | targets: [ 17 | .target( 18 | name: "ColorKit", 19 | path: "ColorKit/ColorKit"), 20 | .testTarget( 21 | name: "ColorKitTests", 22 | dependencies: ["ColorKit"], 23 | path: "ColorKit/ColorKitTests"), 24 | ], 25 | swiftLanguageVersions: [SwiftVersion.v5] 26 | ) 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ColorKit 2 | 3 |

4 | 5 | **ColorKit** is your companion to work with colors on iOS. 6 | 7 |

8 | 9 | Build 10 | 11 | 12 | MIT License 13 | 14 | 15 | Swift 5.1 16 | 17 |

18 | 19 | - [Features](#features) 20 | - [Installation](#installation) 21 | - [Sample Project](#sample-project) 22 | - [Contributing](#contributing) 23 | - [License](#license) 24 | 25 |
26 | 27 | ## Features 28 | 29 | ### Dominant Colors 30 | **ColorKit** makes it easy to find the dominant colors of an image. It returns a color palette of the most common colors on the image. 31 | 32 | ```swift 33 | let dominantColors = try image.dominantColors() 34 | ``` 35 | 36 |

37 | 38 |

39 | 40 | By default, **ColorKit** uses an iterative process to determine the dominant colors of an image. But it also supports doing so via a [k-mean clustering algorithm](https://en.wikipedia.org/wiki/K-means_clustering). Choose whichever is more appropriate for your use case. 41 | 42 | --- 43 | 44 | ### Color Palette 45 | **ColorKit** lets you generate color palettes from a collection of colors. It will automatically ensure that the best colors are picked based on a few configurable parameters like contrast ratio. 46 | This feature is particularly powerful when combined with the dominant color calculation. 47 | 48 | ```swift 49 | let colors = try image.dominantColors() 50 | let palette = ColorPalette(orderedColors: colors, ignoreContrastRatio: true) 51 | ``` 52 | The following examples use the palette to dynamically match the color of the text and background to the album covers. 53 | 54 |

55 | 56 |

57 | 58 | --- 59 | 60 | ### Average Color 61 | 62 | To compute the average color of an image, simply call the `averageColor` function on a `UIImage` instance. 63 | ```swift 64 | let averageColor = try image.averageColor() 65 | ``` 66 | 67 | --- 68 | 69 | ### Color Difference (DeltaE) 70 | 71 | Perceptual color difference / comparaison is a common problem of color science. 72 | It simply consists of calculating how different two colors look from each other, to the human eye. This is commonly referenced as the DeltaE. 73 | 74 | **ColorKit** makes it a breaze to compare two colors. 75 | 76 | ```swift 77 | let colorDifference = UIColor.green.difference(from: .white) // 120.34 78 | ``` 79 | 80 | While this may seem trivial, simply using the RGB color model often yields non-accurate results for human perception. 81 | This is because RGB is not perceptually uniform. 82 | 83 | Here is an example highlighting the limitations of using the RGB color model to compare colors. 84 | 85 |

86 | 87 |

88 | 89 | As you can see, the difference between the two greens (left) is considered greater than the difference between the pink and gray colors (right). In other words, the pink and gray are considered to look more similar than the two greens by the algorithm. 90 | This obviously does not match the expectation of the human eye. 91 | 92 | Thankfully, **ColorKit** provides algorithms that make it possible to compare colors just like the human eye would: **CIE76**, **CIE94** and **CIEDE2000**. 93 | 94 | ```swift 95 | let colorDifference = UIColor.green.difference(from: .white, using: .CIE94) 96 | ``` 97 | 98 | Here is the same example as above, using the **CIE94** algorithm. 99 | 100 |

101 | 102 |

103 | 104 | The **CIE94** algorithm successfuly realizes that the two greens (left) look closer from each other than the pink and gray (right) do. 105 | 106 | More information about color difference can be found [here](https://en.wikipedia.org/wiki/Color_difference). 107 | 108 | --- 109 | 110 | ### Contrast Ratio 111 | 112 | To calculate the contrast ratio between two colors, simply use the `contrastRatio` function. 113 | ```swift 114 | let contrastRatio = UIColor.green.contrastRatio(with: UIColor.white) 115 | ``` 116 | The contrast ratio is particularly important when displaying text. 117 | To ensure that it's readable by everyone, **ColorKit** makes it easy for you to follow the accessibility guidelines set by [WCAG 2](https://www.w3.org/WAI/WCAG21/quickref/?versions=2.0#qr-visual-audio-contrast-contrast). 118 | 119 | --- 120 | 121 | ### Color Space Conversions 122 | 123 | **ColorKit** assists you when translating a color from a color space to another. 124 | They're simply supported as extensions on `UIColor`. 125 | **CIELAB**, **XYZ** and **CMYK** are supported. 126 | 127 | --- 128 | 129 | ### More 130 | 131 | There is a lot more that **ColorKit** is capable of. 132 | Here is a short list of examples: 133 | - Working with Hex color codes 134 | ```swift 135 | let hexValue = UIColor.green.hex 136 | let color = UIColor(hex: "eb4034") 137 | ``` 138 | - Generating random colors 139 | ```swift 140 | let randomColor = UIColor.random() 141 | ``` 142 | - Calculating the relative luminance of a color 143 | ```swift 144 | let relativeLuminance = UIColor.green.relativeLuminance 145 | ``` 146 | - Generating complementary colors 147 | ```swift 148 | let complementaryColor = UIColor.green.complementaryColor 149 | ``` 150 | 151 |
152 | 153 | ## Installation 154 | 155 | ### Swift Package Manager 156 | 157 | The [Swift Package Manager](https://swift.org/package-manager/) is the easiest way to install and manage **ColorKit** as a dependecy. 158 | Simply add **ColorKit** to your dependencies in your `Package.swift` file: 159 | ```swift 160 | dependencies: [ 161 | .package(url: "https://github.com/Boris-Em/ColorKit.git") 162 | ] 163 | ``` 164 | 165 | Alternatively, you can also use XCode to add **ColorKit** to your existing project, by using `File > Swift Packages > Add Package Dependency...`. 166 | 167 | ### Manually 168 | 169 | **ColorKit** can also be added to your project manually. Download the **ColorKit** project from Github, then drag and drop the folder `ColorKit/ColorKit` into your XCode project. 170 | 171 |
172 | 173 | ## Sample Project 174 | 175 | Use the iOS sample project included in this repository to find comprehensive examples of the different features of **ColorKit**. 176 | 177 |
178 | 179 | ## Contributing 180 | 181 | Contributions to **ColorKit** are always welcome! 182 | For bugs and feature requests, open an [issue](https://github.com/Boris-Em/ColorKit/issues/new). 183 | To contribute to the code base, simply submit a [pull request](https://github.com/Boris-Em/ColorKit/pulls). 184 | 185 |
186 | 187 | ## License 188 | 189 | See the [License](https://github.com/Boris-Em/ColorKit/blob/master/LICENSE). You are free to make changes and use this in either personal or commercial projects. Attribution is not required, but highly appreciated. A little "Thanks!" (or something to that affect) is always welcome. If you use **ColorKit** in one of your projects, please let us know! 190 | --------------------------------------------------------------------------------