├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .swiftlint.yml ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTORS.md ├── DynamicColor.podspec ├── Examples ├── DynamicColor │ ├── DynamicColor.h │ ├── DynamicColor.swift │ └── Info.plist ├── DynamicColorExample.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ ├── DynamicColor.xcscheme │ │ ├── DynamicColorMacOS.xcscheme │ │ ├── DynamicColorTvOs.xcscheme │ │ └── DynamicColorWatchOs.xcscheme ├── DynamicColorMacOS │ ├── DynamicColorMacOS.h │ └── Info.plist ├── DynamicColorTvOs │ ├── DynamicColorTvOs.h │ └── Info.plist ├── DynamicColorWatchOs │ ├── DynamicColorWatchOs.h │ └── Info.plist ├── WatchOSExample WatchKit App │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ └── Interface.storyboard │ └── Info.plist ├── WatchOSExample WatchKit Extension │ ├── Assets.xcassets │ │ └── README__ignoredByTemplate__ │ ├── ExtensionDelegate.swift │ ├── Info.plist │ ├── InterfaceController.swift │ └── RowController.swift ├── WatchOSExample.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── WatchOSExample │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift ├── iOSExample │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── ColorCellView.swift │ ├── Example.playground │ │ ├── Contents.swift │ │ └── contents.xcplayground │ ├── HeaderView.swift │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ └── ViewController.swift └── tvOSExample │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── App Icon & Top Shelf Image.brandassets │ │ ├── App Icon - Large.imagestack │ │ │ ├── Back.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Front.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ └── Middle.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ ├── App Icon - Small.imagestack │ │ │ ├── Back.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Front.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ └── Middle.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── Top Shelf Image.imageset │ │ │ └── Contents.json │ ├── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json │ ├── Base.lproj │ └── Main.storyboard │ └── Info.plist ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── Core │ ├── Array.swift │ ├── ContrastDisplayContext.swift │ ├── DynamicColor+Deriving.swift │ ├── DynamicColor+HSB.swift │ ├── DynamicColor+HSL.swift │ ├── DynamicColor+Lab.swift │ ├── DynamicColor+Mixing.swift │ ├── DynamicColor+RGBA.swift │ ├── DynamicColor+XYZ.swift │ ├── DynamicColor.swift │ └── DynamicGradient.swift ├── Shared │ ├── DynamicColorSpace.swift │ ├── GrayscalingMode.swift │ ├── HSL.swift │ └── Utils.swift └── SwiftUI │ ├── Color.swift │ ├── DynamicColor+Color.swift │ └── HSL+Color.swift ├── Tests ├── DynamicColor+HSBTests.swift ├── DynamicColor+HSLTests.swift ├── DynamicColor+RGBATests.swift ├── DynamicColor+XCTAssertEqual.swift ├── DynamicColor+XYZTests.swift ├── DynamicColorArrayTests.swift ├── DynamicColorLabTests.swift ├── DynamicColorTests.swift └── DynamicGradientTests.swift └── codecov.yml /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Smartphone (please complete the following information):** 21 | - Device: [e.g. iPhone6] 22 | - OS: [e.g. iOS8.1] 23 | 24 | **Additional context** 25 | Add any other context about the problem here. 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | *.xccheckout 19 | *.txt 20 | .swiftpm/* 21 | .build/* 22 | DynamicColor.xcodeproj -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | # - colon 3 | # - comma 4 | # - control_statement 5 | # - file_length 6 | # - force_cast 7 | # - force_try 8 | # - function_body_length 9 | # - leading_whitespace 10 | # - line_length 11 | # - nesting 12 | # - opening_brace 13 | # - operator_whitespace 14 | # - return_arrow_whitespace 15 | # - statement_position 16 | # - todo 17 | # - trailing_newline 18 | # - trailing_semicolon 19 | # - trailing_whitespace 20 | # - type_body_length 21 | # - type_name 22 | # - variable_name_max_length 23 | # - variable_name_min_length 24 | - variable_name 25 | - large_tuple 26 | line_length: 27 | - 150 # warning 28 | excluded: 29 | - Examples/ 30 | statement_position: 31 | statement_mode: uncuddled_else 32 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode11 3 | script: 4 | - brew install swiftlint || brew upgrade swiftlint 5 | - swiftlint lint --path Sources/ --config ../.swiftlint.yml 6 | - swift package -c release generate-xcodeproj 7 | - xcodebuild -version 8 | - xcodebuild -project DynamicColor.xcodeproj -scheme DynamicColor-Package -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 8" -configuration Release ONLY_ACTIVE_ARCH=YES -enableCodeCoverage YES test 9 | - bash <(curl -s https://codecov.io/bash) -cF ios 10 | - xcodebuild -project DynamicColor.xcodeproj -scheme DynamicColor-Package -sdk macosx -configuration Release -enableCodeCoverage YES test 11 | - bash <(curl -s https://codecov.io/bash) -cF osx 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ## [Version 5.0.0](https://github.com/yannickl/DynamicColor/releases/tag/5.0.0) 4 | *Released on 2019-12-24.* 5 | 6 | - [ADD] Basic SwiftUI color support 7 | 8 | ## [Version 4.1.1](https://github.com/yannickl/DynamicColor/releases/tag/4.2.0) 9 | *Released on 2019-09-15.* 10 | 11 | - [FIX] Add parenthesis arround mathematical operators to avoid precedence issues (#51) 12 | - [ADD] `toRGBA` method 13 | - [ADD] `toAGBR` method 14 | 15 | ## [Version 4.1.0](https://github.com/yannickl/DynamicColor/releases/tag/4.1.0) 16 | *Released on 2019-05-27.* 17 | 18 | - [FIX] Minor improvements (compilation performance, documentation) 19 | - Swift 5/Xcode 10 support 20 | 21 | ## [Version 4](https://github.com/yannickl/DynamicColor/releases/tag/4) 22 | *Released on 2017-11-07.* 23 | 24 | - [FIX] Minor improvements (compilation performance, documentation) 25 | - Swift 4/Xcode 9 support 26 | 27 | ## [Version 3.3](https://github.com/yannickl/DynamicColor/releases/tag/3.3) 28 | *Released on 2017-04-15.* 29 | 30 | - [ADD] Support alpha channel with the hex strings (e.g. #FF0934CC) 31 | 32 | ## [Version 3.2.1](https://github.com/yannickl/DynamicColor/releases/tag/3.2.1) 33 | *Released on 2016-12-15.* 34 | 35 | - [FIX] `mixed` method with the alpha channel 36 | 37 | ## [Version 3.2.0](https://github.com/yannickl/DynamicColor/releases/tag/3.2.0) 38 | *Released on 2016-12-12.* 39 | 40 | - [ADD] `luminance` property 41 | - [ADD] `contrastRatio` method 42 | - [ADD] `isContrasting` method 43 | 44 | ## [Version 3.1.0](https://github.com/yannickl/DynamicColor/releases/tag/3.1.0) 45 | *Released on 2016-09-08.* 46 | 47 | - [ADD] CIE XYZ Color Space 48 | - [ADD] Initialization with XYZ components 49 | - [ADD] `toXYZComponents()` method 50 | - [ADD] CIE L*a*b* Color Space 51 | - [ADD] Initialization with L*a*b* components 52 | - [ADD] `toLabComponents()` method 53 | - [ADD] `toHSBComponents()` method 54 | - [REFACTORING] `toHSLAComponents` to `toHSLComponents` 55 | - [REFACTORING] Hue range is now from 0° and 360° instead of 0.0 and 1.0 56 | - [ADD] `DynamicGradient` object 57 | - [ADD] `colorPalette` method to a gradient 58 | - [ADD] `pickColorAt` method to a gradient 59 | - [ADD] `gradient` property to array of colors 60 | - [ADD] `DynamicColorSpace` enum 61 | - [REFACTORING] `mixed` colors using different color spaces 62 | 63 | ## [Version 3.0.0](https://github.com/yannickl/DynamicColor/releases/tag/3.0.0) 64 | *Released on 2016-06-14.* 65 | 66 | - Swift 3 Supports 67 | - `isLight` instead of `isLightColor` 68 | - `adjustedAlpha` instead of `adjustedAlphaColor` 69 | - `inverted` instead of `invertColor` 70 | - `grayscaled` instead of `grayscaledColor` 71 | - `darkened` instead of `darkerColor` 72 | - `lighter` instead of `lighterColor` 73 | - `saturated` instead of `saturatedColor` 74 | - `desaturated` instead of `desaturatedColor` 75 | - `complemented` instead of `complementColor` 76 | - `adjustedHue` instead of `adjustedHueColor` 77 | - `tinted` instead of `tintColor` 78 | - `shaded` instead of `shadeColor` 79 | - `mixed` instead of `mixWithColor` 80 | - `isEqual:toHex` instead of `isEqualToHex` 81 | - `isEqual:toHexString` instead of `isEqualToHexString` 82 | - Removing the `darkenColor`, use `darkened` instead 83 | - Removing the `lightenColor`, use `lighter` instead 84 | - Removing the `saturateColor`, use `saturated` instead 85 | - Removing the `desaturateColor`, use `desaturated` instead 86 | - Use `CGFloat` instead of `Double` everywhere 87 | 88 | ## [Version 2.4.0](https://github.com/yannickl/DynamicColor/releases/tag/2.4.0) 89 | *Released on 2016-02-29.* 90 | 91 | - [ADD] Swift Package Manager supports 92 | 93 | ## [Version 2.3.0](https://github.com/yannickl/DynamicColor/releases/tag/2.3.0) 94 | *Released on 2015-12-12.* 95 | 96 | - [ADD] `adjustedAlphaColor` method 97 | - [REFACTORING] Move `redComponent/greenComponent/blueComponent/alphaComponent` methods to properties in order to reflect the OSX API 98 | 99 | ## [Version 2.2.0](https://github.com/yannickl/DynamicColor/releases/tag/2.2.0) 100 | *Released on 2015-11-19.* 101 | 102 | - [ADD] `isLightColor` method 103 | - [REFACTORING] `red/green/blue/alpha` methods to `redComponent/greenComponent/blueComponent/alphaComponent` 104 | - [ADD] OSX supports 105 | 106 | ## [Version 2.1.0](https://github.com/yannickl/DynamicColor/releases/tag/2.1.0) 107 | *Released on 2015-10-29.* 108 | 109 | - [ADD] WatchOS 2 supports 110 | - [ADD] TVOS 9 supports 111 | 112 | ## [Version 2.0.1](https://github.com/yannickl/DynamicColor/releases/tag/2.0.1) 113 | *Released on 2015-10-26.* 114 | 115 | - [FIX] BITCODE supports ([#6](https://github.com/yannickl/DynamicColor/pull/6)) 116 | 117 | ## [Version 2.0.0](https://github.com/yannickl/DynamicColor/releases/tag/2.0.0) 118 | *Released on 2015-09-17.* 119 | 120 | - [UPDATE] Swift 2 compatibility 121 | - [FIX] Use Double instead of CGFloat due to float precision 122 | 123 | ## [Version 1.5.4](https://github.com/yannickl/DynamicColor/releases/tag/1.5.4) 124 | *Released on 2015-09-01.* 125 | 126 | - [FIX] Mandatory clip parameters for Xcode7 beta 6 ([#5](https://github.com/yannickl/DynamicColor/pull/5)) 127 | 128 | ## [Version 1.5.3](https://github.com/yannickl/DynamicColor/releases/tag/1.5.3) 129 | *Released on 2015-08-29.* 130 | 131 | - [UPDATE] Make the DynamicColor typealias public 132 | 133 | ## [Version 1.5.2](https://github.com/yannickl/DynamicColor/releases/tag/1.5.2) 134 | Released on 2015-08-27. 135 | 136 | - [ADD] Changelog.md file 137 | 138 | ## [Version 1.5.1](https://github.com/yannickl/DynamicColor/releases/tag/1.5.1) 139 | *Released on 2015-07-29.* 140 | 141 | - [FIX] Project framework target sets to 8.0 for Carthage support ([#4](https://github.com/yannickl/DynamicColor/pull/4)) 142 | 143 | ## [Version 1.5.0](https://github.com/yannickl/DynamicColor/releases/tag/1.5.0) 144 | *Released on 2015-07-28.* 145 | 146 | - [ADD] Initialization with hue 147 | - [ADD] `toHSLAComponents` method 148 | 149 | ## [Version 1.4.0](https://github.com/yannickl/DynamicColor/releases/tag/1.4.0) 150 | *Released on 2015-07-24.* 151 | 152 | - [ADD] `toHex` method 153 | - [ADD] `isEqualToHex` method 154 | 155 | ## [Version 1.3.0](https://github.com/yannickl/DynamicColor/releases/tag/1.3.0) 156 | *Released on 2015-07-09.* 157 | 158 | - [UPDATE] Documentation 159 | - [FIX] Some var to let ([#3](https://github.com/yannickl/DynamicColor/pull/3)) 160 | - [ADD] `toComponents` method 161 | - [ADD] `red/green/blue/alpha` methods 162 | 163 | ## [Version 1.2.0](https://github.com/yannickl/DynamicColor/releases/tag/1.2.0) 164 | *Released on 2015-06-12.* 165 | 166 | - [ADD] Carthage support 167 | 168 | ## [Version 1.1.1](https://github.com/yannickl/DynamicColor/releases/tag/1.1.1) 169 | *Released on 2015-06-10.* 170 | 171 | - [FIX] Typos in the documentation ([#1](https://github.com/yannickl/DynamicColor/pull/1)) 172 | - [IMPROVEMENT] Check interval for parameter 173 | 174 | ## [Version 1.1.0](https://github.com/yannickl/DynamicColor/releases/tag/1.1.0) 175 | *Released on 2015-06-06.* 176 | 177 | - [ADD] `shadeColor` method 178 | - [ADD] `tintColor` method 179 | - [ADD] `mixWithColor` method 180 | 181 | ## [Version 1.0.0](https://github.com/yannickl/DynamicColor/releases/tag/1.0.0) 182 | *Released on 2015-06-02.* 183 | 184 | - `saturated` method 185 | - `desaturate` method 186 | - `lighter/lighten` methods 187 | - `darker/darken` methods 188 | - `grayscale` method 189 | - `adjustHue` method 190 | - `complementColor` method 191 | - `invertColor` method 192 | - `toHexString` method 193 | - `isEqualToHexString` method 194 | - Initialization with hex strings and integers 195 | - Cocoapods support 196 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | ## Contributors 2 | This is the official list of people who can contribute (and typically have contributed) code to the `DynamicColor` repository. 3 | 4 | Names should be added to this file like so: 5 | ``` * [Firstname Lastname|Nickname](github_page_url)``` 6 | 7 | Please keep the list sorted. 8 | 9 | ### Lead developer 10 | 11 | * [Yannick Loriot](https://github.com/yannickl) 12 | 13 | ### People and companies, who have contributed 14 | 15 | * [Bas Broek](https://github.com/BasThomas) 16 | * [Dmitry Frishbuter](https://github.com/DmitryFrishbuter) 17 | * [Julien Quéré](https://github.com/Onejjy) 18 | * [Naoyashiga](https://github.com/naoyashiga) 19 | * [Paulo Cesar](https://github.com/puelocesar) 20 | * [Tanin Rojanapiansatith](https://github.com/landtanin) 21 | * [Tobias Due Munk](https://github.com/duemunk) 22 | -------------------------------------------------------------------------------- /DynamicColor.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'DynamicColor' 3 | s.version = '5.0.1' 4 | s.license = 'MIT' 5 | s.swift_version = ['5.0', '5.1'] 6 | s.summary = 'Yet another extension to manipulate colors easily in Swift (UIColor, NSColor and SwiftUI)' 7 | s.homepage = 'https://github.com/yannickl/DynamicColor.git' 8 | s.social_media_url = 'https://twitter.com/yannickloriot' 9 | s.authors = { 'Yannick Loriot' => 'contact@yannickloriot.com' } 10 | s.source = { :git => 'https://github.com/yannickl/DynamicColor.git', :tag => s.version } 11 | s.screenshot = 'http://yannickloriot.com/resources/dynamiccolor-sample-screenshot.png' 12 | 13 | s.ios.deployment_target = '11.0' 14 | s.osx.deployment_target = '10.11' 15 | s.tvos.deployment_target = '11.0' 16 | s.watchos.deployment_target = '4.0' 17 | 18 | s.ios.framework = 'UIKit' 19 | s.osx.framework = 'AppKit' 20 | s.tvos.framework = 'UIKit' 21 | s.watchos.framework = 'UIKit' 22 | 23 | s.source_files = 'Sources/**/*.swift' 24 | s.requires_arc = true 25 | end 26 | -------------------------------------------------------------------------------- /Examples/DynamicColor/DynamicColor.h: -------------------------------------------------------------------------------- 1 | // 2 | // DynamicColor.h 3 | // DynamicColor 4 | // 5 | // Created by Yannick Loriot on 16/07/2020. 6 | // Copyright © 2020 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for DynamicColor. 12 | FOUNDATION_EXPORT double DynamicColorVersionNumber; 13 | 14 | //! Project version string for DynamicColor. 15 | FOUNDATION_EXPORT const unsigned char DynamicColorVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Examples/DynamicColor/DynamicColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DynamicColor.swift 3 | // DynamicColor 4 | // 5 | // Created by Yannick Loriot on 16/07/2020. 6 | // Copyright © 2020 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | class DynamicColor { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /Examples/DynamicColor/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 | -------------------------------------------------------------------------------- /Examples/DynamicColorExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/DynamicColorExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/DynamicColorExample.xcodeproj/xcshareddata/xcschemes/DynamicColor.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 55 | 61 | 62 | 63 | 64 | 65 | 66 | 72 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /Examples/DynamicColorExample.xcodeproj/xcshareddata/xcschemes/DynamicColorMacOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Examples/DynamicColorExample.xcodeproj/xcshareddata/xcschemes/DynamicColorTvOs.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Examples/DynamicColorExample.xcodeproj/xcshareddata/xcschemes/DynamicColorWatchOs.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Examples/DynamicColorMacOS/DynamicColorMacOS.h: -------------------------------------------------------------------------------- 1 | // 2 | // DynamicColorMacOS.h 3 | // DynamicColorMacOS 4 | // 5 | // Created by Yannick LORIOT on 15/01/2017. 6 | // Copyright © 2017 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for DynamicColorMacOS. 12 | FOUNDATION_EXPORT double DynamicColorMacOSVersionNumber; 13 | 14 | //! Project version string for DynamicColorMacOS. 15 | FOUNDATION_EXPORT const unsigned char DynamicColorMacOSVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Examples/DynamicColorMacOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSHumanReadableCopyright 22 | Copyright © 2017 Yannick LORIOT. All rights reserved. 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Examples/DynamicColorTvOs/DynamicColorTvOs.h: -------------------------------------------------------------------------------- 1 | // 2 | // DynamicColorTvOs.h 3 | // DynamicColorTvOs 4 | // 5 | // Created by Yannick LORIOT on 15/01/2017. 6 | // Copyright © 2017 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for DynamicColorTvOs. 12 | FOUNDATION_EXPORT double DynamicColorTvOsVersionNumber; 13 | 14 | //! Project version string for DynamicColorTvOs. 15 | FOUNDATION_EXPORT const unsigned char DynamicColorTvOsVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Examples/DynamicColorTvOs/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Examples/DynamicColorWatchOs/DynamicColorWatchOs.h: -------------------------------------------------------------------------------- 1 | // 2 | // DynamicColorWatchOs.h 3 | // DynamicColorWatchOs 4 | // 5 | // Created by Yannick LORIOT on 15/01/2017. 6 | // Copyright © 2017 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for DynamicColorWatchOs. 12 | FOUNDATION_EXPORT double DynamicColorWatchOsVersionNumber; 13 | 14 | //! Project version string for DynamicColorWatchOs. 15 | FOUNDATION_EXPORT const unsigned char DynamicColorWatchOsVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Examples/DynamicColorWatchOs/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Examples/WatchOSExample WatchKit App/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "24x24", 5 | "idiom" : "watch", 6 | "scale" : "2x", 7 | "role" : "notificationCenter", 8 | "subtype" : "38mm" 9 | }, 10 | { 11 | "size" : "27.5x27.5", 12 | "idiom" : "watch", 13 | "scale" : "2x", 14 | "role" : "notificationCenter", 15 | "subtype" : "42mm" 16 | }, 17 | { 18 | "size" : "29x29", 19 | "idiom" : "watch", 20 | "role" : "companionSettings", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "size" : "29x29", 25 | "idiom" : "watch", 26 | "role" : "companionSettings", 27 | "scale" : "3x" 28 | }, 29 | { 30 | "size" : "40x40", 31 | "idiom" : "watch", 32 | "scale" : "2x", 33 | "role" : "appLauncher", 34 | "subtype" : "38mm" 35 | }, 36 | { 37 | "size" : "86x86", 38 | "idiom" : "watch", 39 | "scale" : "2x", 40 | "role" : "quickLook", 41 | "subtype" : "38mm" 42 | }, 43 | { 44 | "size" : "98x98", 45 | "idiom" : "watch", 46 | "scale" : "2x", 47 | "role" : "quickLook", 48 | "subtype" : "42mm" 49 | } 50 | ], 51 | "info" : { 52 | "version" : 1, 53 | "author" : "xcode" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Examples/WatchOSExample WatchKit App/Base.lproj/Interface.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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
36 |
37 | 38 | 39 | 40 |
41 |
42 | 43 |
44 |
45 |
46 | -------------------------------------------------------------------------------- /Examples/WatchOSExample WatchKit App/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | WatchOSExample WatchKit App 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | UISupportedInterfaceOrientations 26 | 27 | UIInterfaceOrientationPortrait 28 | UIInterfaceOrientationPortraitUpsideDown 29 | 30 | WKCompanionAppBundleIdentifier 31 | com.yannickloriot.WatchOSExample 32 | WKWatchKitApp 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Examples/WatchOSExample WatchKit Extension/Assets.xcassets/README__ignoredByTemplate__: -------------------------------------------------------------------------------- 1 | Did you know that git does not support storing empty directories? 2 | -------------------------------------------------------------------------------- /Examples/WatchOSExample WatchKit Extension/ExtensionDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExtensionDelegate.swift 3 | // WatchOSExample WatchKit Extension 4 | // 5 | // Created by Yannick LORIOT on 29/10/15. 6 | // Copyright © 2015 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | import WatchKit 10 | 11 | class ExtensionDelegate: NSObject, WKExtensionDelegate { 12 | 13 | func applicationDidFinishLaunching() { 14 | // Perform any final initialization of your application. 15 | } 16 | 17 | func applicationDidBecomeActive() { 18 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 19 | } 20 | 21 | func applicationWillResignActive() { 22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 23 | // Use this method to pause ongoing tasks, disable timers, etc. 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Examples/WatchOSExample WatchKit Extension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | WatchOSExample WatchKit Extension 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | NSExtension 26 | 27 | NSExtensionAttributes 28 | 29 | WKAppBundleIdentifier 30 | com.yannickloriot.WatchOSExample.watchkitapp 31 | 32 | NSExtensionPointIdentifier 33 | com.apple.watchkit 34 | 35 | RemoteInterfacePrincipalClass 36 | $(PRODUCT_MODULE_NAME).InterfaceController 37 | WKExtensionDelegateClassName 38 | $(PRODUCT_MODULE_NAME).ExtensionDelegate 39 | 40 | 41 | -------------------------------------------------------------------------------- /Examples/WatchOSExample WatchKit Extension/InterfaceController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InterfaceController.swift 3 | // WatchOSExample Extension 4 | // 5 | // Created by Yannick LORIOT on 29/10/15. 6 | // Copyright © 2015 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | import WatchKit 10 | import Foundation 11 | import DynamicColor 12 | 13 | class InterfaceController: WKInterfaceController { 14 | @IBOutlet var tableView: WKInterfaceTable! 15 | 16 | private lazy var colors: [(String, UIColor)] = { 17 | let mainColor = DynamicColor(hexString: "#c0392b") 18 | 19 | return [ 20 | ("Original", mainColor), 21 | ("Lighter", mainColor.lighter()), 22 | ("Darkened", mainColor.darkened()), 23 | ("Saturated", mainColor.saturated()), 24 | ("Desaturated", mainColor.desaturated()), 25 | ("Grayscaled", mainColor.grayscaled()), 26 | ("Adjusted", mainColor.adjustedHue(amount: 45)), 27 | ("Complemented", mainColor.complemented()), 28 | ("Inverted", mainColor.inverted()), 29 | ("Mix Blue", mainColor.mixed(withColor: .blue)), 30 | ("Mix Green", mainColor.mixed(withColor: .green)), 31 | ("Mix Yellow", mainColor.mixed(withColor: .yellow)), 32 | ("Tinted", mainColor.tinted()), 33 | ("Shaded", mainColor.shaded()) 34 | ] 35 | }() 36 | 37 | override func awake(withContext context: Any?) { 38 | super.awake(withContext: context) 39 | 40 | // Configure interface objects here. 41 | tableView.setNumberOfRows(colors.count, withRowType: "ColorRow") 42 | 43 | for (index, (identifier, color)) in colors.enumerated() { 44 | if let cell = tableView.rowController(at: index) as? RowController { 45 | cell.colorNameLabel.setText(identifier) 46 | cell.colorView.setBackgroundColor(color) 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Examples/WatchOSExample WatchKit Extension/RowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RowController.swift 3 | // WatchOSExample Extension 4 | // 5 | // Created by Yannick LORIOT on 29/10/15. 6 | // Copyright © 2015 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | import WatchKit 10 | import Foundation 11 | 12 | class RowController: NSObject { 13 | @IBOutlet var colorNameLabel: WKInterfaceLabel! 14 | @IBOutlet var colorView: WKInterfaceGroup! 15 | } 16 | -------------------------------------------------------------------------------- /Examples/WatchOSExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/WatchOSExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/WatchOSExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // WatchOSExample 4 | // 5 | // Created by Yannick LORIOT on 29/10/15. 6 | // Copyright © 2015 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Examples/WatchOSExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Examples/WatchOSExample/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 | 27 | 28 | -------------------------------------------------------------------------------- /Examples/WatchOSExample/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 | -------------------------------------------------------------------------------- /Examples/WatchOSExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Examples/WatchOSExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // WatchOSExample 4 | // 5 | // Created by Yannick LORIOT on 29/10/15. 6 | // Copyright © 2015 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | } 13 | -------------------------------------------------------------------------------- /Examples/iOSExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // DynamicColorExample 4 | // 5 | // Created by Yannick LORIOT on 01/06/15. 6 | // Copyright (c) 2015 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | func applicationWillResignActive(_ application: UIApplication) { 22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 23 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 24 | } 25 | 26 | func applicationDidEnterBackground(_ application: UIApplication) { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | func applicationWillEnterForeground(_ application: UIApplication) { 32 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 33 | } 34 | 35 | func applicationDidBecomeActive(_ application: UIApplication) { 36 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 37 | } 38 | 39 | func applicationWillTerminate(_ application: UIApplication) { 40 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Examples/iOSExample/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Examples/iOSExample/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 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 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 | 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 | -------------------------------------------------------------------------------- /Examples/iOSExample/ColorCellView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorCellView.swift 3 | // DynamicColorExample 4 | // 5 | // Created by Yannick LORIOT on 02/06/15. 6 | // Copyright (c) 2015 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ColorCellView: UICollectionViewCell { 12 | @IBOutlet weak var colorView: UIView? 13 | @IBOutlet weak var titleLabel: UILabel! 14 | 15 | func layoutColorView() { 16 | if let cv = colorView { 17 | cv.layer.cornerRadius = cv.bounds.width / 2 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Examples/iOSExample/Example.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import DynamicColor 2 | 3 | /*: ### Reference color */ 4 | let ref = DynamicColor(hexString: "#c0392b") 5 | 6 | /*: ### Derivated colors */ 7 | ref.lighter() 8 | ref.darkened() 9 | ref.saturated() 10 | ref.desaturated() 11 | ref.grayscaled() 12 | ref.adjustedHue(amount: 45) 13 | ref.complemented() 14 | ref.inverted() 15 | ref.mixed(withColor: #colorLiteral(red: 0, green: 0, blue: 1, alpha: 1)) 16 | ref.mixed(withColor: #colorLiteral(red: 0, green: 1, blue: 0, alpha: 1)) 17 | ref.mixed(withColor: #colorLiteral(red: 1, green: 1, blue: 0, alpha: 1)) 18 | ref.tinted() 19 | ref.shaded() 20 | 21 | /*: ### Color space components */ 22 | ref.toRGBAComponents() 23 | ref.toHSLComponents() 24 | ref.toXYZComponents() 25 | ref.toLabComponents() 26 | 27 | /*: ### Gradients */ 28 | [#colorLiteral(red: 0.2193539292, green: 0.4209204912, blue: 0.1073316187, alpha: 1), #colorLiteral(red: 0.9446166754, green: 0.6509571671, blue: 0.1558967829, alpha: 1)].gradient.colorPalette(amount: 5) 29 | DynamicColor(red: 0.219, green: 0.421, blue: 0.107, alpha: 1) 30 | DynamicColor(red: 0.703, green: 0.574, blue: 0.14, alpha: 1) 31 | DynamicColor(red: 0.945, green: 0.651, blue: 0.156, alpha: 1) 32 | [#colorLiteral(red: 0.2193539292, green: 0.4209204912, blue: 0.1073316187, alpha: 1), #colorLiteral(red: 1, green: 0, blue: 0, alpha: 1), #colorLiteral(red: 0.1991284192, green: 0.6028449535, blue: 0.9592232704, alpha: 1)].gradient.pickColorAt(scale: 0.8) 33 | -------------------------------------------------------------------------------- /Examples/iOSExample/Example.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Examples/iOSExample/HeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HeaderView.swift 3 | // DynamicColorExample 4 | // 5 | // Created by Yannick LORIOT on 05/09/16. 6 | // Copyright © 2016 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class HeaderView: UICollectionReusableView { 12 | @IBOutlet weak var titleLabel: UILabel! 13 | 14 | } 15 | -------------------------------------------------------------------------------- /Examples/iOSExample/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Examples/iOSExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Examples/iOSExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // DynamicColorExample 4 | // 5 | // Created by Yannick LORIOT on 01/06/15. 6 | // Copyright (c) 2015 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import DynamicColor 11 | 12 | class ViewController: UIViewController { 13 | private let colorCellIdentifier = "ColorCell" 14 | 15 | @IBOutlet weak var colorCollectionView: UICollectionView! 16 | 17 | private lazy var colors: [(String, UIColor)] = { 18 | let mainColor = UIColor(hexString: "#c0392b") 19 | 20 | return [ 21 | ("Original", mainColor), 22 | ("Lighter", mainColor.lighter()), 23 | ("Darkered", mainColor.darkened()), 24 | ("Saturated", mainColor.saturated()), 25 | ("Desaturated", mainColor.desaturated()), 26 | ("Grayscaled", mainColor.grayscaled()), 27 | ("Adjusted", mainColor.adjustedHue(amount: 45)), 28 | ("Complemented", mainColor.complemented()), 29 | ("Inverted", mainColor.inverted()), 30 | ("Mix Blue", mainColor.mixed(withColor: .blue)), 31 | ("Mix Green", mainColor.mixed(withColor: .green)), 32 | ("Mix Yellow", mainColor.mixed(withColor: .yellow)), 33 | ("Tinted", mainColor.tinted()), 34 | ("Shaded", mainColor.shaded()) 35 | ] 36 | }() 37 | 38 | private lazy var gradients: [(String, UIColor)] = { 39 | return [UIColor(hex: 0x3498db), UIColor(hex: 0xe74c3c), UIColor(hex: 0xf1c40f)].gradient.colorPalette(amount: 15).map { ($0.toHexString(), $0) } 40 | }() 41 | 42 | func collection(inSection section: Int) -> [(String, UIColor)] { 43 | return section == 0 ? colors : gradients 44 | } 45 | } 46 | 47 | extension ViewController: UICollectionViewDataSource { 48 | func numberOfSections(in collectionView: UICollectionView) -> Int { 49 | return 2 50 | } 51 | 52 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 53 | return collection(inSection: section).count 54 | } 55 | 56 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 57 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: colorCellIdentifier, for: indexPath) 58 | 59 | self.collectionView(collectionView, willDisplay: cell, forItemAt: indexPath) 60 | 61 | return cell 62 | } 63 | 64 | func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { 65 | // swiftlint:disable force_cast 66 | let supplementaryView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "HeaderView", for: indexPath) as! HeaderView 67 | supplementaryView.titleLabel.text = indexPath.section == 0 ? "Colors" : "Gradients" 68 | 69 | return supplementaryView 70 | } 71 | } 72 | 73 | 74 | extension ViewController: UICollectionViewDelegate { 75 | func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { 76 | guard let cell = cell as? ColorCellView else { return } 77 | 78 | let (title, color) = collection(inSection: indexPath.section)[indexPath.row] 79 | 80 | cell.titleLabel?.text = title 81 | cell.colorView?.backgroundColor = color 82 | 83 | cell.layoutColorView() 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Examples/tvOSExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TVExample 4 | // 5 | // Created by Yannick LORIOT on 29/10/15. 6 | // Copyright © 2015 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | func applicationWillResignActive(_ application: UIApplication) { 21 | // Sent when the application is about to move from active to inactive state. 22 | // This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) 23 | // or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. 25 | // Games should use this method to pause the game. 26 | } 27 | 28 | func applicationDidEnterBackground(_ application: UIApplication) { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough 30 | // application state information to restore your application to its current state in case it is terminated later. 31 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 32 | } 33 | 34 | func applicationWillEnterForeground(_ application: UIApplication) { 35 | // Called as part of the transition from the background to the inactive state; 36 | // here you can undo many of the changes made on entering the background. 37 | } 38 | 39 | func applicationDidBecomeActive(_ application: UIApplication) { 40 | // Restart any tasks that were paused (or not yet started) while the application was inactive. 41 | // If the application was previously in the background, optionally refresh the user interface. 42 | } 43 | 44 | func applicationWillTerminate(_ application: UIApplication) { 45 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Examples/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Examples/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Examples/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "size" : "1280x768", 5 | "idiom" : "tv", 6 | "filename" : "App Icon - Large.imagestack", 7 | "role" : "primary-app-icon" 8 | }, 9 | { 10 | "size" : "400x240", 11 | "idiom" : "tv", 12 | "filename" : "App Icon - Small.imagestack", 13 | "role" : "primary-app-icon" 14 | }, 15 | { 16 | "size" : "1920x720", 17 | "idiom" : "tv", 18 | "filename" : "Top Shelf Image.imageset", 19 | "role" : "top-shelf-image" 20 | } 21 | ], 22 | "info" : { 23 | "version" : 1, 24 | "author" : "xcode" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Examples/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/tvOSExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/tvOSExample/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "landscape", 5 | "idiom" : "tv", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "9.0", 8 | "scale" : "1x" 9 | } 10 | ], 11 | "info" : { 12 | "version" : 1, 13 | "author" : "xcode" 14 | } 15 | } -------------------------------------------------------------------------------- /Examples/tvOSExample/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 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 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 | 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 | -------------------------------------------------------------------------------- /Examples/tvOSExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | arm64 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present Yannick Loriot 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "DynamicColor", 6 | platforms: [ 7 | .iOS(SupportedPlatform.IOSVersion.v11) 8 | ], 9 | products: [ 10 | .library(name: "DynamicColor", targets: ["DynamicColor"]), 11 | ], 12 | targets: [ 13 | .target( 14 | name: "DynamicColor", 15 | dependencies: [], 16 | path: "Sources" 17 | ), 18 | .testTarget( 19 | name: "DynamicColorTests", 20 | dependencies: ["DynamicColor"], 21 | path: "Tests" 22 | ), 23 | ] 24 | ) 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | DynamicColor 3 |

4 | 5 |

6 | Supported Platforms 7 | Version 8 | Carthage compatible 9 | Swift Package Manager compatible 10 | Build status 11 | 12 |

13 | 14 | **DynamicColor** provides powerful methods to manipulate colors in an easy way in Swift and SwiftUI. 15 | 16 |

17 | example screenshot 18 | example screenshot 19 |

20 | 21 |

22 | RequirementsUsageInstallationContributionContactLicense 23 |

24 | 25 | ## Requirements 26 | 27 | - iOS 11.0+ / Mac OS X 10.11+ / tvOS 11.0+ / watchOS 4.0+ 28 | - Xcode 10.2+ 29 | - Swift 5.0+ 30 | 31 | ## Usage 32 | 33 | #### Creation (Hex String) 34 | 35 | Firstly, DynamicColor provides useful initializers to create colors using hex strings or values: 36 | 37 | ```swift 38 | let color = UIColor(hexString: "#3498db") 39 | // equivalent to 40 | // color = UIColor(hex: 0x3498db) 41 | ``` 42 | 43 | To be platform independent, the typealias `DynamicColor` can also be used: 44 | 45 | ```swift 46 | let color = DynamicColor(hex: 0x3498db) 47 | // On iOS, WatchOS or tvOS, equivalent to 48 | // color = UIColor(hex: 0x3498db) 49 | // On OSX, equivalent to 50 | // color = NSColor(hex: 0x3498db) 51 | ``` 52 | 53 | You can also retrieve the RGBA value and components very easily using multiple methods like `toHexString`, `toHex`, `toRGBA`, etc. 54 | 55 | ##### SwiftUI 56 | 57 | From the v5, DynamicColor also support basic methods to create and manipulate colors with SwiftUI. 58 | 59 | ```swift 60 | let color = Color(hex: 0x3498db) 61 | ``` 62 | 63 | #### Darken & Lighten 64 | 65 | These two create a new color by adjusting the lightness of the receiver. You have to use a value between 0 and 1. 66 | 67 |

68 | lighten and darken color 69 |

70 | 71 | ```swift 72 | let originalColor = DynamicColor(hexString: "#c0392b") 73 | 74 | let lighterColor = originalColor.lighter() 75 | // equivalent to 76 | // lighterColor = originalColor.lighter(amount: 0.2) 77 | 78 | let darkerColor = originalColor.darkened() 79 | // equivalent to 80 | // darkerColor = originalColor.darkened(amount: 0.2) 81 | ``` 82 | 83 | #### Saturate, Desaturate & Grayscale 84 | 85 | These will adjust the saturation of the color object, much like `darkened` and `lighter` adjusted the lightness. Again, you need to use a value between 0 and 1. 86 | 87 |

88 | saturate, desaturate and grayscale color 89 |

90 | 91 | ```swift 92 | let originalColor = DynamicColor(hexString: "#c0392b") 93 | 94 | let saturatedColor = originalColor.saturated() 95 | // equivalent to 96 | // saturatedColor = originalColor.saturated(amount: 0.2) 97 | 98 | let desaturatedColor = originalColor.desaturated() 99 | // equivalent to 100 | // desaturatedColor = originalColor.desaturated(amount: 0.2) 101 | 102 | // equivalent to 103 | // let grayscaledColor = originalColor.grayscaled(mode: .luminance) 104 | let grayscaledColor = originalColor.grayscaled() 105 | 106 | let grayscaledColorLuminance = originalColor.grayscaled(mode: .luminance) 107 | let grayscaledColorLightness = originalColor.grayscaled(mode: .lightness) 108 | let grayscaledColorAverage = originalColor.grayscaled(mode: .average) 109 | let grayscaledColorValue = originalColor.grayscaled(mode: .value) 110 | ``` 111 | 112 | #### Adjust-hue & Complement 113 | 114 | These adjust the hue value of the color in the same way like the others do. Again, it takes a value between 0 and 1 to update the value. 115 | 116 |

117 | ajusted-hue and complement color 118 |

119 | 120 | ```swift 121 | let originalColor = DynamicColor(hex: 0xc0392b) 122 | 123 | // Hue values are in degrees 124 | let adjustHueColor = originalColor.adjustedHue(amount: 45) 125 | 126 | let complementedColor = originalColor.complemented() 127 | ```` 128 | 129 | #### Tint & Shade 130 | 131 | A tint is the mixture of a color with white and a shade is the mixture of a color with black. Again, it takes a value between 0 and 1 to update the value. 132 | 133 |

134 | tint and shade color 135 |

136 | 137 | ```swift 138 | let originalColor = DynamicColor(hexString: "#c0392b") 139 | 140 | let tintedColor = originalColor.tinted() 141 | // equivalent to 142 | // tintedColor = originalColor.tinted(amount: 0.2) 143 | 144 | let shadedColor = originalColor.shaded() 145 | // equivalent to 146 | // shadedColor = originalColor.shaded(amount: 0.2) 147 | ``` 148 | 149 | #### Invert 150 | 151 | This can invert the color object. The red, green, and blue values are inverted, while the opacity is left alone. 152 | 153 |

154 | invert color 155 |

156 | 157 | ```swift 158 | let originalColor = DynamicColor(hexString: "#c0392b") 159 | 160 | let invertedColor = originalColor.inverted() 161 | ``` 162 | 163 | #### Mix 164 | 165 | This can mix a given color with the receiver. It takes the average of each of the RGB components, optionally weighted by the given percentage (value between 0 and 1). 166 | 167 |

168 | mix color 169 |

170 | 171 | ```swift 172 | let originalColor = DynamicColor(hexString: "#c0392b") 173 | 174 | let mixedColor = originalColor.mixed(withColor: .blue) 175 | // equivalent to 176 | // mixedColor = originalColor.mixed(withColor: .blue, weight: 0.5) 177 | // or 178 | // mixedColor = originalColor.mixed(withColor: .blue, weight: 0.5, inColorSpace: .rgb) 179 | ``` 180 | 181 | #### Gradients 182 | 183 | **DynamicColor** provides an useful object to work with gradients: **DynamicGradient**. It'll allow you to pick color from gradients, or to build a palette using different color spaces (.e.g.: *RGB*, *HSL*, *HSB*, *Cie L\*a\*b\**). 184 | 185 | Let's define our reference colors and the gradient object: 186 | ```swift 187 | let blue = UIColor(hexString: "#3498db") 188 | let red = UIColor(hexString: "#e74c3c") 189 | let yellow = UIColor(hexString: "#f1c40f") 190 | 191 | let gradient = DynamicGradient(colors: [blue, red, yellow]) 192 | // equivalent to 193 | // let gradient = [blue, red, yellow].gradient 194 | ``` 195 | 196 | ##### RGB 197 | 198 | Let's build the RGB palette (the default color space) with 8 colors: 199 | 200 |

201 | RGB gradient 202 |

203 | 204 | ```swift 205 | let rgbPalette = gradient.colorPalette(amount: 8) 206 | ``` 207 | 208 | ##### HSL 209 | 210 | Now if you want to change the gradient color space to have a different effect, just write the following lines: 211 | 212 |

213 | HSL gradient 214 |

215 | 216 | ```swift 217 | let hslPalette = gradient.colorPalette(amount: 8, inColorSpace: .hsl) 218 | ``` 219 | 220 | ##### Cie L\*a\*b\* 221 | 222 | Or if you prefer to work directly with array of colors, you can: 223 | 224 |

225 | Cie L*a*b* gradient 226 |

227 | 228 | ```swift 229 | let labPalette = [blue, red, yellow].gradient.colorPalette(amount: 8, inColorSpace: .lab) 230 | ``` 231 | 232 | #### And many more... 233 | 234 | `DynamicColor` also provides many another useful methods to manipulate the colors like hex strings, color components, color spaces, etc. To go further, take a look at the example project. 235 | 236 | ## Installation 237 | 238 | #### CocoaPods 239 | 240 | Install CocoaPods if not already available: 241 | 242 | ``` bash 243 | $ [sudo] gem install cocoapods 244 | $ pod setup 245 | ``` 246 | Go to the directory of your Xcode project, and Create and Edit your *Podfile* and add _DynamicColor_: 247 | 248 | ``` bash 249 | $ cd /path/to/MyProject 250 | $ touch Podfile 251 | $ edit Podfile 252 | source 'https://github.com/CocoaPods/Specs.git' 253 | platform :ios, '8.0' 254 | 255 | use_frameworks! 256 | pod 'DynamicColor', '~> 5.0.0' 257 | ``` 258 | 259 | Install into your project: 260 | 261 | ``` bash 262 | $ pod install 263 | ``` 264 | 265 | Open your project in Xcode from the .xcworkspace file (not the usual project file): 266 | 267 | ``` bash 268 | $ open MyProject.xcworkspace 269 | ``` 270 | 271 | You can now `import DynamicColor` framework into your files. 272 | 273 | #### Carthage 274 | 275 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that automates the process of adding frameworks to your Cocoa application. 276 | 277 | You can install Carthage with [Homebrew](http://brew.sh/) using the following command: 278 | 279 | ```bash 280 | $ brew update 281 | $ brew install carthage 282 | ``` 283 | 284 | To integrate `DynamicColor` into your Xcode project using Carthage, specify it in your `Cartfile` file: 285 | 286 | ```ogdl 287 | github "yannickl/DynamicColor" >= 5.0.0 288 | ``` 289 | 290 | #### Swift Package Manager 291 | You can use [The Swift Package Manager](https://swift.org/package-manager) to install `DynamicColor` by adding the proper description to your `Package.swift` file: 292 | ```swift 293 | import PackageDescription 294 | 295 | let package = Package( 296 | name: "YOUR_PROJECT_NAME", 297 | targets: [], 298 | dependencies: [ 299 | .package(url: "https://github.com/yannickl/DynamicColor.git", from: "5.0.0") 300 | ] 301 | ) 302 | ``` 303 | 304 | Note that the [Swift Package Manager](https://swift.org/package-manager) is still in early design and development, for more information checkout its [GitHub Page](https://github.com/apple/swift-package-manager). 305 | 306 | #### Manually 307 | 308 | [Download](https://github.com/YannickL/DynamicColor/archive/master.zip) the project and copy the `DynamicColor` folder into your project to use it in. 309 | 310 | ## Contribution 311 | 312 | Contributions are welcomed and encouraged *♡*. 313 | 314 | ## Contact 315 | 316 | Yannick Loriot 317 | - [https://21.co/yannickl/](https://21.co/yannickl/) 318 | - [https://twitter.com/yannickloriot](https://twitter.com/yannickloriot) 319 | 320 | ## License (MIT) 321 | 322 | Copyright (c) 2015-present - Yannick Loriot 323 | 324 | Permission is hereby granted, free of charge, to any person obtaining a copy 325 | of this software and associated documentation files (the "Software"), to deal 326 | in the Software without restriction, including without limitation the rights 327 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 328 | copies of the Software, and to permit persons to whom the Software is 329 | furnished to do so, subject to the following conditions: 330 | 331 | The above copyright notice and this permission notice shall be included in 332 | all copies or substantial portions of the Software. 333 | 334 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 335 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 336 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 337 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 338 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 339 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 340 | THE SOFTWARE. 341 | -------------------------------------------------------------------------------- /Sources/Core/Array.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import Foundation 28 | 29 | /** 30 | Convenient extension for color array to work as a DynamicGradient. 31 | */ 32 | public extension Array where Element: DynamicColor { 33 | /** 34 | Gradient representation of the array. 35 | */ 36 | var gradient: DynamicGradient { 37 | return DynamicGradient(colors: self) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Core/ContrastDisplayContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContrastDisplayContext.swift 3 | // DynamicColorExample 4 | // 5 | // Created by Yannick LORIOT on 26/11/2016. 6 | // Copyright © 2016 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if os(iOS) || os(tvOS) || os(watchOS) 12 | import UIKit 13 | #elseif os(OSX) 14 | import AppKit 15 | #endif 16 | 17 | extension DynamicColor { 18 | /** 19 | Used to describe the context of display of 2 colors. 20 | 21 | Based on WCAG: https://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast-contrast 22 | */ 23 | public enum ContrastDisplayContext { 24 | /** 25 | A standard text in a normal context. 26 | */ 27 | case standard 28 | /** 29 | A large text in a normal context. 30 | You can look here for the definition of "large text": 31 | https://www.w3.org/TR/2008/REC-WCAG20-20081211/#larger-scaledef 32 | */ 33 | case standardLargeText 34 | /** 35 | A standard text in an enhanced context. 36 | Enhanced means that you want to be accessible (and AAA compliant in WCAG) 37 | */ 38 | case enhanced 39 | /** 40 | A large text in an enhanced context. 41 | Enhanced means that you want to be accessible (and AAA compliant in WCAG) 42 | You can look here for the definition of "large text": 43 | https://www.w3.org/TR/2008/REC-WCAG20-20081211/#larger-scaledef 44 | */ 45 | case enhancedLargeText 46 | 47 | var minimumContrastRatio: CGFloat { 48 | switch self { 49 | case .standard: 50 | return 4.5 51 | case .standardLargeText: 52 | return 3.0 53 | case .enhanced: 54 | return 7.0 55 | case .enhancedLargeText: 56 | return 4.5 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/Core/DynamicColor+Deriving.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | #if os(iOS) || os(tvOS) || os(watchOS) 28 | import UIKit 29 | #elseif os(OSX) 30 | import AppKit 31 | #endif 32 | 33 | // MARK: Deriving Colors 34 | 35 | public extension DynamicColor { 36 | /** 37 | Creates and returns a color object with the hue rotated along the color wheel by the given amount. 38 | 39 | - parameter amount: A float representing the number of degrees as ratio (usually between -360.0 degree and 360.0 degree). 40 | - returns: A DynamicColor object with the hue changed. 41 | */ 42 | final func adjustedHue(amount: CGFloat) -> DynamicColor { 43 | return HSL(color: self).adjustedHue(amount: amount).toDynamicColor() 44 | } 45 | 46 | /** 47 | Creates and returns the complement of the color object. 48 | 49 | This is identical to adjustedHue(180). 50 | 51 | - returns: The complement DynamicColor. 52 | - seealso: adjustedHueColor: 53 | */ 54 | final func complemented() -> DynamicColor { 55 | return adjustedHue(amount: 180.0) 56 | } 57 | 58 | /** 59 | Creates and returns a color object with the lightness increased by the given amount. 60 | 61 | - parameter amount: CGFloat between 0.0 and 1.0. Default value is 0.2. 62 | - returns: A lighter DynamicColor. 63 | */ 64 | final func lighter(amount: CGFloat = 0.2) -> DynamicColor { 65 | return HSL(color: self).lighter(amount: amount).toDynamicColor() 66 | } 67 | 68 | /** 69 | Creates and returns a color object with the lightness decreased by the given amount. 70 | 71 | - parameter amount: Float between 0.0 and 1.0. Default value is 0.2. 72 | - returns: A darker DynamicColor. 73 | */ 74 | final func darkened(amount: CGFloat = 0.2) -> DynamicColor { 75 | return HSL(color: self).darkened(amount: amount).toDynamicColor() 76 | } 77 | 78 | /** 79 | Creates and returns a color object with the saturation increased by the given amount. 80 | 81 | - parameter amount: CGFloat between 0.0 and 1.0. Default value is 0.2. 82 | 83 | - returns: A DynamicColor more saturated. 84 | */ 85 | final func saturated(amount: CGFloat = 0.2) -> DynamicColor { 86 | return HSL(color: self).saturated(amount: amount).toDynamicColor() 87 | } 88 | 89 | /** 90 | Creates and returns a color object with the saturation decreased by the given amount. 91 | 92 | - parameter amount: CGFloat between 0.0 and 1.0. Default value is 0.2. 93 | - returns: A DynamicColor less saturated. 94 | */ 95 | final func desaturated(amount: CGFloat = 0.2) -> DynamicColor { 96 | return HSL(color: self).desaturated(amount: amount).toDynamicColor() 97 | } 98 | 99 | /** 100 | Creates and returns a color object converted to grayscale. 101 | 102 | - returns: A grayscale DynamicColor. 103 | - seealso: desaturated: 104 | */ 105 | final func grayscaled(mode: GrayscalingMode = .lightness) -> DynamicColor { 106 | let (r, g, b, a) = self.toRGBAComponents() 107 | 108 | let l: CGFloat 109 | switch mode { 110 | case .luminance: 111 | l = (0.299 * r) + (0.587 * g) + (0.114 * b) 112 | case .lightness: 113 | l = 0.5 * (max(r, g, b) + min(r, g, b)) 114 | case .average: 115 | l = (1.0 / 3.0) * (r + g + b) 116 | case .value: 117 | l = max(r, g, b) 118 | } 119 | 120 | return HSL(hue: 0.0, saturation: 0.0, lightness: l, alpha: a).toDynamicColor() 121 | } 122 | 123 | /** 124 | Creates and return a color object where the red, green, and blue values are inverted, while the alpha channel is left alone. 125 | 126 | - returns: An inverse (negative) of the original color. 127 | */ 128 | final func inverted() -> DynamicColor { 129 | let rgba = toRGBAComponents() 130 | 131 | let invertedRed = 1.0 - rgba.r 132 | let invertedGreen = 1.0 - rgba.g 133 | let invertedBlue = 1.0 - rgba.b 134 | 135 | return DynamicColor(red: invertedRed, green: invertedGreen, blue: invertedBlue, alpha: rgba.a) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Sources/Core/DynamicColor+HSB.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | #if os(iOS) || os(tvOS) || os(watchOS) 28 | import UIKit 29 | #elseif os(OSX) 30 | import AppKit 31 | #endif 32 | 33 | // MARK: HSB Color Space 34 | 35 | extension DynamicColor { 36 | // MARK: - Getting the HSB Components 37 | 38 | /** 39 | Returns the HSB (hue, saturation, brightness) components. 40 | 41 | - returns: The HSB components as a tuple (h, s, b). 42 | */ 43 | public final func toHSBComponents() -> (h: CGFloat, s: CGFloat, b: CGFloat) { 44 | var h: CGFloat = 0.0 45 | var s: CGFloat = 0.0 46 | var b: CGFloat = 0.0 47 | 48 | #if os(iOS) || os(tvOS) || os(watchOS) 49 | getHue(&h, saturation: &s, brightness: &b, alpha: nil) 50 | 51 | return (h: h, s: s, b: b) 52 | #elseif os(OSX) 53 | if isEqual(DynamicColor.black) { 54 | return (0.0, 0.0, 0.0) 55 | } 56 | else if isEqual(DynamicColor.white) { 57 | return (0.0, 0.0, 1.0) 58 | } 59 | 60 | getHue(&h, saturation: &s, brightness: &b, alpha: nil) 61 | 62 | return (h: h, s: s, b: b) 63 | #endif 64 | } 65 | 66 | #if os(iOS) || os(tvOS) || os(watchOS) 67 | /** 68 | The hue component as CGFloat between 0.0 to 1.0. 69 | */ 70 | public final var hueComponent: CGFloat { 71 | return toHSBComponents().h 72 | } 73 | 74 | /** 75 | The saturation component as CGFloat between 0.0 to 1.0. 76 | */ 77 | public final var saturationComponent: CGFloat { 78 | return toHSBComponents().s 79 | } 80 | 81 | /** 82 | The brightness component as CGFloat between 0.0 to 1.0. 83 | */ 84 | public final var brightnessComponent: CGFloat { 85 | return toHSBComponents().b 86 | } 87 | #endif 88 | } 89 | -------------------------------------------------------------------------------- /Sources/Core/DynamicColor+HSL.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | #if os(iOS) || os(tvOS) || os(watchOS) 28 | import UIKit 29 | #elseif os(OSX) 30 | import AppKit 31 | #endif 32 | 33 | // MARK: HSL Color Space 34 | 35 | extension DynamicColor { 36 | /** 37 | Initializes and returns a color object using the specified opacity and HSL component values. 38 | 39 | - parameter hue: The hue component of the color object, specified as a value from 0.0 to 360.0 degree. 40 | - parameter saturation: The saturation component of the color object, specified as a value from 0.0 to 1.0. 41 | - parameter lightness: The lightness component of the color object, specified as a value from 0.0 to 1.0. 42 | - parameter alpha: The opacity value of the color object, specified as a value from 0.0 to 1.0. Default to 1.0. 43 | */ 44 | public convenience init(hue: CGFloat, saturation: CGFloat, lightness: CGFloat, alpha: CGFloat = 1) { 45 | let color = HSL(hue: hue, saturation: saturation, lightness: lightness, alpha: alpha).toDynamicColor() 46 | let components = color.toRGBAComponents() 47 | 48 | self.init(red: components.r, green: components.g, blue: components.b, alpha: components.a) 49 | } 50 | 51 | // MARK: - Getting the HSL Components 52 | 53 | /** 54 | Returns the HSL (hue, saturation, lightness) components. 55 | 56 | Notes that the hue value is between 0.0 and 360.0 degree. 57 | 58 | - returns: The HSL components as a tuple (h, s, l). 59 | */ 60 | public final func toHSLComponents() -> (h: CGFloat, s: CGFloat, l: CGFloat) { 61 | let hsl = HSL(color: self) 62 | 63 | return (hsl.h * 360.0, hsl.s, hsl.l) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/Core/DynamicColor+Lab.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | #if os(iOS) || os(tvOS) || os(watchOS) 28 | import UIKit 29 | #elseif os(OSX) 30 | import AppKit 31 | #endif 32 | 33 | // MARK: CIE L*a*b* Color Space 34 | 35 | public extension DynamicColor { 36 | /** 37 | Initializes and returns a color object using CIE XYZ color space component values with an observer at 2° and a D65 illuminant. 38 | 39 | Notes that values out of range are clipped. 40 | 41 | - parameter L: The lightness, specified as a value from 0 to 100.0. 42 | - parameter a: The red-green axis, specified as a value from -128.0 to 127.0. 43 | - parameter b: The yellow-blue axis, specified as a value from -128.0 to 127.0. 44 | - parameter alpha: The opacity value of the color object, specified as a value from 0.0 to 1.0. Default to 1.0. 45 | */ 46 | convenience init(L: CGFloat, a: CGFloat, b: CGFloat, alpha: CGFloat = 1) { 47 | let clippedL = clip(L, 0.0, 100.0) 48 | let clippedA = clip(a, -128.0, 127.0) 49 | let clippedB = clip(b, -128.0, 127.0) 50 | 51 | let normalized = { (c: CGFloat) -> CGFloat in 52 | pow(c, 3) > 0.008856 ? pow(c, 3) : (c - (16 / 116)) / 7.787 53 | } 54 | 55 | let preY = (clippedL + 16.0) / 116.0 56 | let preX = (clippedA / 500.0) + preY 57 | let preZ = preY - (clippedB / 200.0) 58 | 59 | let X = 95.05 * normalized(preX) 60 | let Y = 100.0 * normalized(preY) 61 | let Z = 108.9 * normalized(preZ) 62 | 63 | self.init(X: X, Y: Y, Z: Z, alpha: alpha) 64 | } 65 | 66 | // MARK: - Getting the L*a*b* Components 67 | 68 | /** 69 | Returns the Lab (lightness, red-green axis, yellow-blue axis) components. 70 | It is based on the CIE XYZ color space with an observer at 2° and a D65 illuminant. 71 | 72 | Notes that L values are between 0 to 100.0, a values are between -128 to 127.0 and b values are between -128 to 127.0. 73 | 74 | - returns: The L*a*b* components as a tuple (L, a, b). 75 | */ 76 | final func toLabComponents() -> (L: CGFloat, a: CGFloat, b: CGFloat) { 77 | let normalized = { (c: CGFloat) -> CGFloat in 78 | c > 0.008856 ? pow(c, 1.0 / 3.0) : (7.787 * c) + (16.0 / 116.0) 79 | } 80 | 81 | let xyz = toXYZComponents() 82 | let normalizedX = normalized(xyz.X / 95.05) 83 | let normalizedY = normalized(xyz.Y / 100.0) 84 | let normalizedZ = normalized(xyz.Z / 108.9) 85 | 86 | let L = roundDecimal((116.0 * normalizedY) - 16.0, precision: 1000) 87 | let a = roundDecimal(500.0 * (normalizedX - normalizedY), precision: 1000) 88 | let b = roundDecimal(200.0 * (normalizedY - normalizedZ), precision: 1000) 89 | 90 | return (L: L, a: a, b: b) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Sources/Core/DynamicColor+Mixing.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | #if os(iOS) || os(tvOS) || os(watchOS) 28 | import UIKit 29 | #elseif os(OSX) 30 | import AppKit 31 | #endif 32 | 33 | // MARK: Mixing Colors 34 | 35 | public extension DynamicColor { 36 | /** 37 | Mixes the given color object with the receiver. 38 | 39 | Specifically, takes the average of each of the RGB components, optionally weighted by the given percentage. 40 | 41 | - Parameter color: A color object to mix with the receiver. 42 | - Parameter weight: The weight specifies the amount of the given color object (between 0 and 1). 43 | The default value is 0.5, which means that half the given color and half the receiver color object should be used. 44 | 0.25 means that a quarter of the given color object and three quarters of the receiver color object should be used. 45 | - Parameter colorspace: The color space used to mix the colors. By default it uses the RBG color space. 46 | - Returns: A color object corresponding to the two colors object mixed together. 47 | */ 48 | final func mixed(withColor color: DynamicColor, weight: CGFloat = 0.5, inColorSpace colorspace: DynamicColorSpace = .rgb) -> DynamicColor { 49 | let normalizedWeight = clip(weight, 0.0, 1.0) 50 | 51 | switch colorspace { 52 | case .lab: 53 | return mixedLab(withColor: color, weight: normalizedWeight) 54 | case .hsl: 55 | return mixedHSL(withColor: color, weight: normalizedWeight) 56 | case .hsb: 57 | return mixedHSB(withColor: color, weight: normalizedWeight) 58 | case .rgb: 59 | return mixedRGB(withColor: color, weight: normalizedWeight) 60 | } 61 | } 62 | 63 | /** 64 | Creates and returns a color object corresponding to the mix of the receiver and an amount of white color, which increases lightness. 65 | 66 | - Parameter amount: Float between 0.0 and 1.0. The default amount is equal to 0.2. 67 | - Returns: A lighter DynamicColor. 68 | */ 69 | final func tinted(amount: CGFloat = 0.2) -> DynamicColor { 70 | return mixed(withColor: .white, weight: amount) 71 | } 72 | 73 | /** 74 | Creates and returns a color object corresponding to the mix of the receiver and an amount of black color, which reduces lightness. 75 | 76 | - Parameter amount: Float between 0.0 and 1.0. The default amount is equal to 0.2. 77 | - Returns: A darker DynamicColor. 78 | */ 79 | final func shaded(amount: CGFloat = 0.2) -> DynamicColor { 80 | return mixed(withColor: DynamicColor(red: 0, green: 0, blue: 0, alpha: 1), weight: amount) 81 | } 82 | 83 | // MARK: - Convenient Internal Methods 84 | 85 | func mixedLab(withColor color: DynamicColor, weight: CGFloat) -> DynamicColor { 86 | let c1 = toLabComponents() 87 | let c2 = color.toLabComponents() 88 | 89 | let L = c1.L + (weight * (c2.L - c1.L)) 90 | let a = c1.a + (weight * (c2.a - c1.a)) 91 | let b = c1.b + (weight * (c2.b - c1.b)) 92 | let alpha = alphaComponent + (weight * (color.alphaComponent - alphaComponent)) 93 | 94 | return DynamicColor(L: L, a: a, b: b, alpha: alpha) 95 | } 96 | 97 | func mixedHSL(withColor color: DynamicColor, weight: CGFloat) -> DynamicColor { 98 | let c1 = toHSLComponents() 99 | let c2 = color.toHSLComponents() 100 | 101 | let h = c1.h + (weight * mixedHue(source: c1.h, target: c2.h)) 102 | let s = c1.s + (weight * (c2.s - c1.s)) 103 | let l = c1.l + (weight * (c2.l - c1.l)) 104 | let alpha = alphaComponent + (weight * (color.alphaComponent - alphaComponent)) 105 | 106 | return DynamicColor(hue: h, saturation: s, lightness: l, alpha: alpha) 107 | } 108 | 109 | func mixedHSB(withColor color: DynamicColor, weight: CGFloat) -> DynamicColor { 110 | let c1 = toHSBComponents() 111 | let c2 = color.toHSBComponents() 112 | 113 | let h = c1.h + (weight * mixedHue(source: c1.h, target: c2.h)) 114 | let s = c1.s + (weight * (c2.s - c1.s)) 115 | let b = c1.b + (weight * (c2.b - c1.b)) 116 | let alpha = alphaComponent + (weight * (color.alphaComponent - alphaComponent)) 117 | 118 | return DynamicColor(hue: h, saturation: s, brightness: b, alpha: alpha) 119 | } 120 | 121 | func mixedRGB(withColor color: DynamicColor, weight: CGFloat) -> DynamicColor { 122 | let c1 = toRGBAComponents() 123 | let c2 = color.toRGBAComponents() 124 | 125 | let red = c1.r + (weight * (c2.r - c1.r)) 126 | let green = c1.g + (weight * (c2.g - c1.g)) 127 | let blue = c1.b + (weight * (c2.b - c1.b)) 128 | let alpha = alphaComponent + (weight * (color.alphaComponent - alphaComponent)) 129 | 130 | return DynamicColor(red: red, green: green, blue: blue, alpha: alpha) 131 | } 132 | 133 | func mixedHue(source: CGFloat, target: CGFloat) -> CGFloat { 134 | if target > source && target - source > 180.0 { 135 | return target - source + 360.0 136 | } 137 | else if target < source && source - target > 180.0 { 138 | return target + 360.0 - source 139 | } 140 | 141 | return target - source 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Sources/Core/DynamicColor+RGBA.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | #if os(iOS) || os(tvOS) || os(watchOS) 28 | import UIKit 29 | #elseif os(OSX) 30 | import AppKit 31 | #endif 32 | 33 | // MARK: RGBA Color Space 34 | 35 | public extension DynamicColor { 36 | /** 37 | Initializes and returns a color object using the specified opacity and RGB component values. 38 | 39 | Notes that values out of range are clipped. 40 | 41 | - Parameter r: The red component of the color object, specified as a value from 0.0 to 255.0. 42 | - Parameter g: The green component of the color object, specified as a value from 0.0 to 255.0. 43 | - Parameter b: The blue component of the color object, specified as a value from 0.0 to 255.0. 44 | - Parameter a: The opacity value of the color object, specified as a value from 0.0 to 255.0. The default value is 255. 45 | */ 46 | convenience init(r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat = 255) { 47 | self.init(red: clip(r, 0, 255) / 255, green: clip(g, 0, 255) / 255, blue: clip(b, 0, 255) / 255, alpha: clip(a, 0, 255) / 255) 48 | } 49 | 50 | // MARK: - Getting the RGBA Components 51 | 52 | /** 53 | Returns the RGBA (red, green, blue, alpha) components. 54 | 55 | - returns: The RGBA components as a tuple (r, g, b, a). 56 | */ 57 | final func toRGBAComponents() -> (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) { 58 | var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0 59 | 60 | #if os(iOS) || os(tvOS) || os(watchOS) 61 | getRed(&r, green: &g, blue: &b, alpha: &a) 62 | 63 | return (r, g, b, a) 64 | #elseif os(OSX) 65 | guard let rgbaColor = self.usingColorSpace(.deviceRGB) else { 66 | fatalError("Could not convert color to RGBA.") 67 | } 68 | 69 | rgbaColor.getRed(&r, green: &g, blue: &b, alpha: &a) 70 | 71 | return (r, g, b, a) 72 | #endif 73 | } 74 | 75 | #if os(iOS) || os(tvOS) || os(watchOS) 76 | /** 77 | The red component as CGFloat between 0.0 to 1.0. 78 | */ 79 | final var redComponent: CGFloat { 80 | return toRGBAComponents().r 81 | } 82 | 83 | /** 84 | The green component as CGFloat between 0.0 to 1.0. 85 | */ 86 | final var greenComponent: CGFloat { 87 | return toRGBAComponents().g 88 | } 89 | 90 | /** 91 | The blue component as CGFloat between 0.0 to 1.0. 92 | */ 93 | final var blueComponent: CGFloat { 94 | return toRGBAComponents().b 95 | } 96 | 97 | /** 98 | The alpha component as CGFloat between 0.0 to 1.0. 99 | */ 100 | final var alphaComponent: CGFloat { 101 | return toRGBAComponents().a 102 | } 103 | #endif 104 | 105 | // MARK: - Setting the RGBA Components 106 | 107 | /** 108 | Creates and returns a color object with the alpha increased by the given amount. 109 | 110 | - parameter amount: CGFloat between 0.0 and 1.0. 111 | - returns: A color object with its alpha channel modified. 112 | */ 113 | final func adjustedAlpha(amount: CGFloat) -> DynamicColor { 114 | let components = toRGBAComponents() 115 | let normalizedAlpha = clip(components.a + amount, 0.0, 1.0) 116 | 117 | return DynamicColor(red: components.r, green: components.g, blue: components.b, alpha: normalizedAlpha) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Sources/Core/DynamicColor+XYZ.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | #if os(iOS) || os(tvOS) || os(watchOS) 28 | import UIKit 29 | #elseif os(OSX) 30 | import AppKit 31 | #endif 32 | 33 | // MARK: CIE XYZ Color Space 34 | 35 | public extension DynamicColor { 36 | /** 37 | Initializes and returns a color object using CIE XYZ color space component values with an observer at 2° and a D65 illuminant. 38 | 39 | Notes that values out of range are clipped. 40 | 41 | - parameter X: The mix of cone response curves, specified as a value from 0 to 95.05. 42 | - parameter Y: The luminance, specified as a value from 0 to 100.0. 43 | - parameter Z: The quasi-equal to blue stimulation, specified as a value from 0 to 108.9. 44 | - parameter alpha: The opacity value of the color object, specified as a value from 0.0 to 1.0. Default to 1.0. 45 | */ 46 | convenience init(X: CGFloat, Y: CGFloat, Z: CGFloat, alpha: CGFloat = 1) { 47 | let clippedX = clip(X, 0.0, 95.05) / 100.0 48 | let clippedY = clip(Y, 0.0, 100) / 100.0 49 | let clippedZ = clip(Z, 0.0, 108.9) / 100.0 50 | 51 | let toRGB = { (c: CGFloat) -> CGFloat in 52 | let rgb = c > 0.0031308 ? 1.055 * pow(c, 1.0 / 2.4) - 0.055 : c * 12.92 53 | 54 | return abs(roundDecimal(rgb, precision: 1000.0)) 55 | } 56 | 57 | let red = toRGB((clippedX * 3.2406) + (clippedY * -1.5372) + (clippedZ * -0.4986)) 58 | let green = toRGB((clippedX * -0.9689) + (clippedY * 1.8758) + (clippedZ * 0.0415)) 59 | let blue = toRGB((clippedX * 0.0557) + (clippedY * -0.2040) + (clippedZ * 1.0570)) 60 | 61 | self.init(red: red, green: green, blue: blue, alpha: alpha) 62 | } 63 | 64 | // MARK: - Getting the XYZ Components 65 | 66 | /** 67 | Returns the XYZ (mix of cone response curves, luminance, quasi-equal to blue stimulation) components with an observer at 2° and a D65 illuminant. 68 | 69 | Notes that X values are between 0 to 95.05, Y values are between 0 to 100.0 and Z values are between 0 to 108.9. 70 | 71 | - returns: The XYZ components as a tuple (X, Y, Z). 72 | */ 73 | final func toXYZComponents() -> (X: CGFloat, Y: CGFloat, Z: CGFloat) { 74 | let toSRGB = { (c: CGFloat) -> CGFloat in 75 | c > 0.04045 ? pow((c + 0.055) / 1.055, 2.4) : c / 12.92 76 | } 77 | 78 | let rgba = toRGBAComponents() 79 | let red = toSRGB(rgba.r) 80 | let green = toSRGB(rgba.g) 81 | let blue = toSRGB(rgba.b) 82 | 83 | let X = roundDecimal(((red * 0.4124) + (green * 0.3576) + (blue * 0.1805)) * 100.0, precision: 1000.0) 84 | let Y = roundDecimal(((red * 0.2126) + (green * 0.7152) + (blue * 0.0722)) * 100.0, precision: 1000.0) 85 | let Z = roundDecimal(((red * 0.0193) + (green * 0.1192) + (blue * 0.9505)) * 100.0, precision: 1000.0) 86 | 87 | return (X: X, Y: Y, Z: Z) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Sources/Core/DynamicColor.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | #if os(iOS) || os(tvOS) || os(watchOS) 28 | import UIKit 29 | 30 | /** 31 | Extension to manipulate colours easily. 32 | 33 | It allows you to work hexadecimal strings and value, HSV and RGB components, derivating colours, and many more... 34 | */ 35 | public typealias DynamicColor = UIColor 36 | #elseif os(OSX) 37 | import AppKit 38 | 39 | /** 40 | Extension to manipulate colours easily. 41 | 42 | It allows you to work hexadecimal strings and value, HSV and RGB components, derivating colours, and many more... 43 | */ 44 | public typealias DynamicColor = NSColor 45 | #endif 46 | 47 | public extension DynamicColor { 48 | // MARK: - Manipulating Hexa-decimal Values and Strings 49 | 50 | /** 51 | Creates a color from an hex string (e.g. "#3498db"). The RGBA string are also supported (e.g. "#3498dbff"). 52 | 53 | If the given hex string is invalid the initialiser will create a black color. 54 | 55 | - parameter hexString: A hexa-decimal color string representation. 56 | */ 57 | convenience init(hexString: String) { 58 | let hexString = hexString.trimmingCharacters(in: .whitespacesAndNewlines) 59 | let scanner = Scanner(string: hexString) 60 | scanner.charactersToBeSkipped = CharacterSet(charactersIn: "#") 61 | 62 | var color: UInt64 = 0 63 | 64 | if scanner.scanHexInt64(&color) { 65 | self.init(hex: color, useAlpha: hexString.count > 7) 66 | } 67 | else { 68 | self.init(hex: 0x000000) 69 | } 70 | } 71 | 72 | /** 73 | Creates a color from an hex integer (e.g. 0x3498db). 74 | 75 | - parameter hex: A hexa-decimal UInt64 that represents a color. 76 | - parameter alphaChannel: If true the given hex-decimal UInt64 includes the alpha channel (e.g. 0xFF0000FF). 77 | */ 78 | convenience init(hex: UInt64, useAlpha alphaChannel: Bool = false) { 79 | let mask = UInt64(0xFF) 80 | let cappedHex = !alphaChannel && hex > 0xffffff ? 0xffffff : hex 81 | 82 | let r = cappedHex >> (alphaChannel ? 24 : 16) & mask 83 | let g = cappedHex >> (alphaChannel ? 16 : 8) & mask 84 | let b = cappedHex >> (alphaChannel ? 8 : 0) & mask 85 | let a = alphaChannel ? cappedHex & mask : 255 86 | 87 | let red = CGFloat(r) / 255.0 88 | let green = CGFloat(g) / 255.0 89 | let blue = CGFloat(b) / 255.0 90 | let alpha = CGFloat(a) / 255.0 91 | 92 | self.init(red: red, green: green, blue: blue, alpha: alpha) 93 | } 94 | 95 | /** 96 | Returns the color representation as hexadecimal string. 97 | 98 | - returns: A string similar to this pattern "#f4003b". 99 | */ 100 | final func toHexString() -> String { 101 | return String(format: "#%06x", toHex()) 102 | } 103 | 104 | /** 105 | Returns the color representation as an integer (without the alpha channel). 106 | 107 | - returns: A UInt32 that represents the hexa-decimal color. 108 | */ 109 | final func toHex() -> UInt32 { 110 | let rgba = toRGBAComponents() 111 | 112 | return roundToHex(rgba.r) << 16 | roundToHex(rgba.g) << 8 | roundToHex(rgba.b) 113 | } 114 | 115 | /** 116 | Returns the RGBA color representation. 117 | 118 | - returns: A UInt32 that represents the color as an RGBA value. 119 | */ 120 | func toRGBA() -> UInt32 { 121 | let rgba = toRGBAComponents() 122 | 123 | return roundToHex(rgba.r) << 24 | roundToHex(rgba.g) << 16 | roundToHex(rgba.b) << 8 | roundToHex(rgba.a) 124 | } 125 | 126 | /** 127 | Returns the AGBR color representation. 128 | 129 | - returns: A UInt32 that represents the color as an AGBR value. 130 | */ 131 | func toAGBR() -> UInt32 { 132 | let rgba = toRGBAComponents() 133 | 134 | return roundToHex(rgba.a) << 24 | roundToHex(rgba.b) << 16 | roundToHex(rgba.g) << 8 | roundToHex(rgba.r) 135 | } 136 | 137 | // MARK: - Identifying and Comparing Colors 138 | 139 | /** 140 | Returns a boolean value that indicates whether the receiver is equal to the given hexa-decimal string. 141 | 142 | - parameter hexString: A hexa-decimal color number representation to be compared to the receiver. 143 | - returns: true if the receiver and the string are equals, otherwise false. 144 | */ 145 | func isEqual(toHexString hexString: String) -> Bool { 146 | return self.toHexString() == hexString 147 | } 148 | 149 | /** 150 | Returns a boolean value that indicates whether the receiver is equal to the given hexa-decimal integer. 151 | 152 | - parameter hex: A UInt32 that represents the hexa-decimal color. 153 | - returns: true if the receiver and the integer are equals, otherwise false. 154 | */ 155 | func isEqual(toHex hex: UInt32) -> Bool { 156 | return self.toHex() == hex 157 | } 158 | 159 | // MARK: - Querying Colors 160 | 161 | /** 162 | Determines if the color object is dark or light. 163 | 164 | It is useful when you need to know whether you should display the text in black or white. 165 | 166 | - returns: A boolean value to know whether the color is light. If true the color is light, dark otherwise. 167 | */ 168 | func isLight() -> Bool { 169 | let components = toRGBAComponents() 170 | let brightness = ((components.r * 299.0) + (components.g * 587.0) + (components.b * 114.0)) / 1000.0 171 | 172 | return brightness >= 0.5 173 | } 174 | 175 | /** 176 | A float value representing the luminance of the current color. May vary from 0 to 1.0. 177 | 178 | We use the formula described by W3C in WCAG 2.0. You can read more here: https://www.w3.org/TR/WCAG20/#relativeluminancedef. 179 | */ 180 | var luminance: CGFloat { 181 | let components = toRGBAComponents() 182 | 183 | let componentsArray = [components.r, components.g, components.b].map { (val) -> CGFloat in 184 | guard val <= 0.03928 else { return pow((val + 0.055) / 1.055, 2.4) } 185 | 186 | return val / 12.92 187 | } 188 | 189 | return (0.2126 * componentsArray[0]) + (0.7152 * componentsArray[1]) + (0.0722 * componentsArray[2]) 190 | } 191 | 192 | /** 193 | Returns a float value representing the contrast ratio between 2 colors. 194 | 195 | We use the formula described by W3C in WCAG 2.0. You can read more here: https://www.w3.org/TR/WCAG20-TECHS/G18.html 196 | NB: the contrast ratio is a relative value. So the contrast between Color1 and Color2 is exactly the same between Color2 and Color1. 197 | 198 | - returns: A CGFloat representing contrast value. 199 | */ 200 | func contrastRatio(with otherColor: DynamicColor) -> CGFloat { 201 | let otherLuminance = otherColor.luminance 202 | 203 | let l1 = max(luminance, otherLuminance) 204 | let l2 = min(luminance, otherLuminance) 205 | 206 | return (l1 + 0.05) / (l2 + 0.05) 207 | } 208 | 209 | /** 210 | Indicates if two colors are contrasting, regarding W3C's WCAG 2.0 recommendations. 211 | 212 | You can read it here: https://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast-contrast 213 | 214 | The acceptable contrast ratio depends on the context of display. Most of the time, the default context (.Standard) is enough. 215 | 216 | You can look at ContrastDisplayContext for more options. 217 | 218 | - parameter otherColor: The other color to compare with. 219 | - parameter context: An optional context to determine the minimum acceptable contrast ratio. Default value is .Standard. 220 | 221 | - returns: true is the contrast ratio between 2 colors exceed the minimum acceptable ratio. 222 | */ 223 | func isContrasting(with otherColor: DynamicColor, inContext context: ContrastDisplayContext = .standard) -> Bool { 224 | return self.contrastRatio(with: otherColor) > context.minimumContrastRatio 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /Sources/Core/DynamicGradient.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | #if os(iOS) || os(tvOS) || os(watchOS) 28 | import UIKit 29 | #elseif os(OSX) 30 | import AppKit 31 | #endif 32 | 33 | /** 34 | Object representing a gradient object. It allows you to manipulate colors inside different gradients and color spaces. 35 | */ 36 | final public class DynamicGradient { 37 | let colors: [DynamicColor] 38 | 39 | /** 40 | Initializes and creates a gradient from a color array. 41 | 42 | - Parameter colors: An array of colors. 43 | */ 44 | public init(colors: [DynamicColor]) { 45 | self.colors = colors 46 | } 47 | 48 | /** 49 | Returns the color palette of `amount` elements by grabbing equidistant colors. 50 | 51 | - Parameter amount: An amount of colors to return. 2 by default. 52 | - Parameter colorspace: The color space used to mix the colors. By default it uses the RBG color space. 53 | - Returns: An array of DynamicColor objects with equi-distant space in the gradient. 54 | */ 55 | public func colorPalette(amount: UInt = 2, inColorSpace colorspace: DynamicColorSpace = .rgb) -> [DynamicColor] { 56 | guard amount > 0 && colors.count > 0 else { 57 | return [] 58 | } 59 | 60 | guard colors.count > 1 else { 61 | return (0 ..< amount).map { _ in colors[0] } 62 | } 63 | 64 | let increment = 1.0 / CGFloat(amount - 1) 65 | 66 | return (0 ..< amount).map { pickColorAt(scale: CGFloat($0) * increment, inColorSpace: colorspace) } 67 | } 68 | 69 | /** 70 | Picks up and returns the color at the given scale by interpolating the colors. 71 | 72 | For example, given this color array `[red, green, blue]` and a scale of `0.25` you will get a kaki color. 73 | 74 | - Parameter scale: A float value between 0.0 and 1.0. 75 | - Parameter colorspace: The color space used to mix the colors. By default it uses the RBG color space. 76 | - Returns: A DynamicColor object corresponding to the color at the given scale. 77 | */ 78 | public func pickColorAt(scale: CGFloat, inColorSpace colorspace: DynamicColorSpace = .rgb) -> DynamicColor { 79 | guard colors.count > 1 else { 80 | return colors.first ?? .black 81 | } 82 | 83 | let clippedScale = clip(scale, 0.0, 1.0) 84 | let positions = (0 ..< colors.count).map { CGFloat($0) / CGFloat(colors.count - 1) } 85 | 86 | var color: DynamicColor = .black 87 | 88 | for (index, position) in positions.enumerated() { 89 | guard clippedScale <= position else { continue } 90 | 91 | guard clippedScale != 0.0 && clippedScale != 1.0 else { 92 | return colors[index] 93 | } 94 | 95 | let previousPosition = positions[index - 1] 96 | let weight = (clippedScale - previousPosition) / (position - previousPosition) 97 | 98 | color = colors[index - 1].mixed(withColor: colors[index], weight: weight, inColorSpace: colorspace) 99 | 100 | break 101 | } 102 | 103 | return color 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Sources/Shared/DynamicColorSpace.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | /** 28 | Defines the supported color spaces. 29 | */ 30 | public enum DynamicColorSpace { 31 | /// The RGB color space 32 | case rgb 33 | /// The HSL color space 34 | case hsl 35 | /// The HSB color space 36 | case hsb 37 | /// The Cie L*a*b* color space 38 | case lab 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Shared/GrayscalingMode.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | /** 28 | Defines the mode (i.e color space) used for grayscaling. 29 | 30 | [More info](https://en.wikipedia.org/wiki/Lightness#Lightness_and_human_perception) 31 | */ 32 | public enum GrayscalingMode { 33 | /// XYZ luminance 34 | case luminance 35 | /// HSL lightness 36 | case lightness 37 | /// RGB average 38 | case average 39 | /// HSV value 40 | case value 41 | } 42 | -------------------------------------------------------------------------------- /Sources/Shared/HSL.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import SwiftUI 28 | 29 | #if os(iOS) || os(tvOS) || os(watchOS) 30 | import UIKit 31 | #elseif os(OSX) 32 | import AppKit 33 | #endif 34 | 35 | /// Hue-saturation-lightness structure to make the color manipulation easier. 36 | internal struct HSL { 37 | /// Hue value between 0.0 and 1.0 (0.0 = 0 degree, 1.0 = 360 degree). 38 | var h: CGFloat = 0.0 39 | /// Saturation value between 0.0 and 1.0. 40 | var s: CGFloat = 0.0 41 | /// Lightness value between 0.0 and 1.0. 42 | var l: CGFloat = 0.0 43 | /// Alpha value between 0.0 and 1.0. 44 | var a: CGFloat = 1.0 45 | 46 | // MARK: - Initializing HSL Colors 47 | 48 | /** 49 | Initializes and creates a HSL color from the hue, saturation, lightness and alpha components. 50 | 51 | - parameter h: The hue component of the color object, specified as a value between 0.0 and 360.0 degree. 52 | - parameter s: The saturation component of the color object, specified as a value between 0.0 and 1.0. 53 | - parameter l: The lightness component of the color object, specified as a value between 0.0 and 1.0. 54 | - parameter a: The opacity component of the color object, specified as a value between 0.0 and 1.0. 55 | */ 56 | init(hue: CGFloat, saturation: CGFloat, lightness: CGFloat, alpha: CGFloat = 1.0) { 57 | h = hue.truncatingRemainder(dividingBy: 360.0) / 360.0 58 | s = clip(saturation, 0.0, 1.0) 59 | l = clip(lightness, 0.0, 1.0) 60 | a = clip(alpha, 0.0, 1.0) 61 | } 62 | 63 | /** 64 | Initializes and creates a HSL (hue, saturation, lightness) color from a DynamicColor object. 65 | 66 | - parameter color: A DynamicColor object. 67 | */ 68 | init(color: DynamicColor) { 69 | let rgba = color.toRGBAComponents() 70 | 71 | let maximum = max(rgba.r, max(rgba.g, rgba.b)) 72 | let minimum = min(rgba.r, min(rgba.g, rgba.b)) 73 | 74 | let delta = maximum - minimum 75 | 76 | h = 0.0 77 | s = 0.0 78 | l = (maximum + minimum) / 2.0 79 | 80 | if delta != 0.0 { 81 | if l < 0.5 { 82 | s = delta / (maximum + minimum) 83 | } 84 | else { 85 | s = delta / (2.0 - maximum - minimum) 86 | } 87 | 88 | if rgba.r == maximum { 89 | h = ((rgba.g - rgba.b) / delta) + (rgba.g < rgba.b ? 6.0 : 0.0) 90 | } 91 | else if rgba.g == maximum { 92 | h = ((rgba.b - rgba.r) / delta) + 2.0 93 | } 94 | else if rgba.b == maximum { 95 | h = ((rgba.r - rgba.g) / delta) + 4.0 96 | } 97 | } 98 | 99 | h /= 6.0 100 | a = rgba.a 101 | } 102 | 103 | // MARK: - Transforming HSL Color 104 | 105 | /** 106 | Returns the DynamicColor representation from the current HSV color. 107 | 108 | - returns: A DynamicColor object corresponding to the current HSV color. 109 | */ 110 | func toDynamicColor() -> DynamicColor { 111 | let (r, g, b, a) = rgbaComponents() 112 | 113 | return DynamicColor(red: r, green: g, blue: b, alpha: a) 114 | } 115 | 116 | /// Returns the RGBA components from the current HSV color. 117 | func rgbaComponents() -> (CGFloat, CGFloat, CGFloat, CGFloat) { 118 | let m2 = l <= 0.5 ? l * (s + 1.0) : (l + s) - (l * s) 119 | let m1 = (l * 2.0) - m2 120 | 121 | let r = hueToRGB(m1: m1, m2: m2, h: h + (1.0 / 3.0)) 122 | let g = hueToRGB(m1: m1, m2: m2, h: h) 123 | let b = hueToRGB(m1: m1, m2: m2, h: h - (1.0 / 3.0)) 124 | 125 | return (r, g, b, CGFloat(a)) 126 | } 127 | 128 | /// Hue to RGB helper function 129 | private func hueToRGB(m1: CGFloat, m2: CGFloat, h: CGFloat) -> CGFloat { 130 | let hue = moda(h, m: 1) 131 | 132 | if hue * 6 < 1.0 { 133 | return m1 + ((m2 - m1) * hue * 6.0) 134 | } 135 | else if hue * 2.0 < 1.0 { 136 | return m2 137 | } 138 | else if hue * 3.0 < 1.9999 { 139 | return m1 + ((m2 - m1) * ((2.0 / 3.0) - hue) * 6.0) 140 | } 141 | 142 | return m1 143 | } 144 | 145 | // MARK: - Deriving the Color 146 | 147 | /** 148 | Returns a color with the hue rotated along the color wheel by the given amount. 149 | 150 | - parameter amount: A float representing the number of degrees as ratio (usually between -360.0 degree and 360.0 degree). 151 | - returns: A HSL color with the hue changed. 152 | */ 153 | func adjustedHue(amount: CGFloat) -> HSL { 154 | return HSL(hue: (h * 360.0) + amount, saturation: s, lightness: l, alpha: a) 155 | } 156 | 157 | /** 158 | Returns a color with the lightness increased by the given amount. 159 | 160 | - parameter amount: CGFloat between 0.0 and 1.0. 161 | - returns: A lighter HSL color. 162 | */ 163 | func lighter(amount: CGFloat) -> HSL { 164 | return HSL(hue: h * 360.0, saturation: s, lightness: l + amount, alpha: a) 165 | } 166 | 167 | /** 168 | Returns a color with the lightness decreased by the given amount. 169 | 170 | - parameter amount: CGFloat between 0.0 and 1.0. 171 | - returns: A darker HSL color. 172 | */ 173 | func darkened(amount: CGFloat) -> HSL { 174 | return lighter(amount: amount * -1.0) 175 | } 176 | 177 | /** 178 | Returns a color with the saturation increased by the given amount. 179 | 180 | - parameter amount: CGFloat between 0.0 and 1.0. 181 | - returns: A HSL color more saturated. 182 | */ 183 | func saturated(amount: CGFloat) -> HSL { 184 | return HSL(hue: h * 360.0, saturation: s + amount, lightness: l, alpha: a) 185 | } 186 | 187 | /** 188 | Returns a color with the saturation decreased by the given amount. 189 | 190 | - parameter amount: CGFloat between 0.0 and 1.0. 191 | - returns: A HSL color less saturated. 192 | */ 193 | func desaturated(amount: CGFloat) -> HSL { 194 | return saturated(amount: amount * -1.0) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /Sources/Shared/Utils.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | #if os(iOS) || os(tvOS) || os(watchOS) 28 | import UIKit 29 | #elseif os(OSX) 30 | import AppKit 31 | #endif 32 | 33 | /** 34 | Clips the values in an interval. 35 | 36 | Given an interval, values outside the interval are clipped to the interval 37 | edges. For example, if an interval of [0, 1] is specified, values smaller than 38 | 0 become 0, and values larger than 1 become 1. 39 | 40 | - Parameter v: The value to clipped. 41 | - Parameter minimum: The minimum edge value. 42 | - Parameter maximum: The maximum edgevalue. 43 | */ 44 | internal func clip(_ v: T, _ minimum: T, _ maximum: T) -> T { 45 | return max(min(v, maximum), minimum) 46 | } 47 | 48 | /** 49 | Returns the absolute value of the modulo operation. 50 | 51 | - Parameter x: The value to compute. 52 | - Parameter m: The modulo. 53 | */ 54 | internal func moda(_ x: CGFloat, m: CGFloat) -> CGFloat { 55 | return (x.truncatingRemainder(dividingBy: m) + m).truncatingRemainder(dividingBy: m) 56 | } 57 | 58 | /** 59 | Rounds the given float to a given decimal precision. 60 | 61 | - Parameter x: The value to round. 62 | - Parameter m: The precision. Default to 10000. 63 | */ 64 | internal func roundDecimal(_ x: CGFloat, precision: CGFloat = 10000.0) -> CGFloat { 65 | return CGFloat(Int(round(x * precision))) / precision 66 | } 67 | 68 | internal func roundToHex(_ x: CGFloat) -> UInt32 { 69 | guard x > 0 else { return 0 } 70 | let rounded: CGFloat = round(x * 255.0) 71 | 72 | return UInt32(rounded) 73 | } 74 | -------------------------------------------------------------------------------- /Sources/SwiftUI/Color.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import SwiftUI 28 | 29 | @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) 30 | public extension Color { 31 | // MARK: - Manipulating Hexa-decimal Values and Strings 32 | 33 | /** 34 | Creates a color from an hex string (e.g. "#3498db"). The RGBA string are also supported (e.g. "#3498dbff"). 35 | 36 | If the given hex string is invalid the initialiser will create a black color. 37 | 38 | - parameter hexString: A hexa-decimal color string representation. 39 | */ 40 | init(hexString: String) { 41 | let hexString = hexString.trimmingCharacters(in: .whitespacesAndNewlines) 42 | let scanner = Scanner(string: hexString) 43 | scanner.charactersToBeSkipped = CharacterSet(charactersIn: "#") 44 | 45 | var color: UInt64 = 0 46 | 47 | if scanner.scanHexInt64(&color) { 48 | self.init(hex: color, useOpacity: hexString.count > 7) 49 | } 50 | else { 51 | self.init(hex: 0x000000) 52 | } 53 | } 54 | 55 | /** 56 | Creates a color from an hex integer (e.g. 0x3498db). 57 | 58 | - parameter hex: A hexa-decimal UInt64 that represents a color. 59 | - parameter opacityChannel: If true the given hex-decimal UInt64 includes the opacity channel (e.g. 0xFF0000FF). 60 | */ 61 | init(hex: UInt64, useOpacity opacityChannel: Bool = false) { 62 | let mask = UInt64(0xFF) 63 | let cappedHex = !opacityChannel && hex > 0xffffff ? 0xffffff : hex 64 | 65 | let r = cappedHex >> (opacityChannel ? 24 : 16) & mask 66 | let g = cappedHex >> (opacityChannel ? 16 : 8) & mask 67 | let b = cappedHex >> (opacityChannel ? 8 : 0) & mask 68 | let o = opacityChannel ? cappedHex & mask : 255 69 | 70 | let red = Double(r) / 255.0 71 | let green = Double(g) / 255.0 72 | let blue = Double(b) / 255.0 73 | let opacity = Double(o) / 255.0 74 | 75 | self.init(red: red, green: green, blue: blue, opacity: opacity) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Sources/SwiftUI/DynamicColor+Color.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import SwiftUI 28 | 29 | #if os(iOS) || os(tvOS) || os(watchOS) 30 | import UIKit 31 | #elseif os(OSX) 32 | import AppKit 33 | #endif 34 | 35 | /// Convert a DynamicColor to a SwiftUI color 36 | extension DynamicColor { 37 | /** 38 | Returns the Color from an Dynamic Color. 39 | 40 | - returns: A Color (SwiftUI). 41 | */ 42 | @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) 43 | func toColor() -> Color { 44 | return Color(self) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/SwiftUI/HSL+Color.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import SwiftUI 28 | 29 | extension HSL { 30 | @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) 31 | func toColor() -> Color { 32 | let (r, g, b, a) = rgbaComponents() 33 | 34 | return SwiftUI.Color(red: Double(r), green: Double(g), blue: Double(b), opacity: Double(a)) 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /Tests/DynamicColor+HSBTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import XCTest 28 | @testable import DynamicColor 29 | 30 | let TestsAcceptedAccuracy = CGFloat(0.000001) 31 | 32 | class DynamicColorHSBTests: XCTestCase { 33 | func testToHSBComponents() { 34 | let customColor = DynamicColor(hue: 0.1, saturation: 0.3, brightness: 0.5, alpha: 1) 35 | let hsb = customColor.toHSBComponents() 36 | 37 | 38 | XCTAssertEqual(hsb.h, 0.1, accuracy: TestsAcceptedAccuracy, "Color hue component should be equal to 0.1 (not \(hsb.h))") 39 | XCTAssertEqual(hsb.s, 0.3, accuracy: TestsAcceptedAccuracy, "Saturation component should be equal to 0.3 (not \(hsb.s))") 40 | XCTAssertEqual(hsb.b, 0.5, accuracy: TestsAcceptedAccuracy, "Brightness component should be equal to 0.3 (not \(hsb.b))") 41 | 42 | let blackHSB = DynamicColor.black.toHSBComponents() 43 | 44 | XCTAssertEqual(blackHSB.h, 0, accuracy: TestsAcceptedAccuracy, "Color hue component should be equal to 0 (not \(blackHSB.h))") 45 | XCTAssertEqual(blackHSB.s, 0, accuracy: TestsAcceptedAccuracy, "Saturation component should be equal to 0 (not \(blackHSB.s))") 46 | XCTAssertEqual(blackHSB.b, 0, accuracy: TestsAcceptedAccuracy, "Brightness component should be equal to 0 (not \(blackHSB.b))") 47 | 48 | let whiteHSB = DynamicColor.white.toHSBComponents() 49 | 50 | XCTAssertEqual(whiteHSB.h, 0, accuracy: TestsAcceptedAccuracy, "Color hue component should be equal to O (not \(whiteHSB.h))") 51 | XCTAssertEqual(whiteHSB.s, 0, accuracy: TestsAcceptedAccuracy, "Saturation component should be equal to 0 (not \(blackHSB.s))") 52 | XCTAssertEqual(whiteHSB.b, 1, accuracy: TestsAcceptedAccuracy, "Brightness component should be equal to 1 (not \(whiteHSB.b))") 53 | 54 | 55 | let redHSB = DynamicColor.red.toHSBComponents() 56 | 57 | XCTAssert(redHSB.h == 1 || redHSB.h == 0, "Color hue component should be equal to 1 or 0 (not \(redHSB.h))") 58 | XCTAssert(redHSB.s == 1, "Saturation component should be equal to 1 (not \(redHSB.s))") 59 | XCTAssert(redHSB.b == 1, "Brightness component should be equal to 1 (not \(redHSB.b))") 60 | } 61 | 62 | func testHueComponent() { 63 | let redHue = DynamicColor.red.hueComponent 64 | 65 | XCTAssert(redHue == 1 || redHue == 0, "Color hue component should be equal to 1 or 0 (not \(redHue))") 66 | 67 | let blackHue = DynamicColor(r: 0, g: 0, b: 0).hueComponent 68 | 69 | XCTAssert(blackHue == 0, "Color hue component should be equal to 0 (not \(blackHue))") 70 | } 71 | 72 | func testSaturationComponent() { 73 | let redSaturation = DynamicColor.red.saturationComponent 74 | 75 | XCTAssert(redSaturation == 1 || redSaturation == 0, "Color saturation component should be equal to 1 or 0 (not \(redSaturation))") 76 | 77 | let blackSaturation = DynamicColor(r: 0, g: 0, b: 0).saturationComponent 78 | 79 | XCTAssert(blackSaturation == 0, "Color saturation component should be equal to 0 (not \(blackSaturation))") 80 | } 81 | 82 | func testBrightnessComponent() { 83 | let redBrightness = DynamicColor.red.brightnessComponent 84 | 85 | XCTAssert(redBrightness == 1 || redBrightness == 0, "Color brightness component should be equal to 1 or 0 (not \(redBrightness))") 86 | 87 | let blackBrightness = DynamicColor(r: 0, g: 0, b: 0).brightnessComponent 88 | 89 | XCTAssert(blackBrightness == 0, "Color brightness component should be equal to 0 (not \(blackBrightness))") 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Tests/DynamicColor+HSLTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import XCTest 28 | @testable import DynamicColor 29 | 30 | class DynamicColorHSLTests: XCTestCase { 31 | func testInitWithHSLComponents() { 32 | let black1 = DynamicColor(hue: 0, saturation: 0, lightness: 0) 33 | let black2 = DynamicColor(hue: 1, saturation: 1, lightness: 0) 34 | let white1 = DynamicColor(hue: 0, saturation: 0, lightness: 1) 35 | let white2 = DynamicColor(hue: 1, saturation: 1, lightness: 1) 36 | 37 | let red = DynamicColor(hue: 0, saturation: 1, lightness: 0.5) 38 | let green = DynamicColor(hue: 120, saturation: 1, lightness: 0.5) 39 | let blue = DynamicColor(hue: 240, saturation: 1, lightness: 0.5) 40 | 41 | let custom = DynamicColor(hue: 6, saturation: 0.781, lightness: 0.571) 42 | 43 | XCTAssert(black1.toHex() == 0, "Color should be black") 44 | XCTAssert(black2.toHex() == 0, "Color should be black") 45 | XCTAssert(white1.toHex() == 0xffffff, "Color should be white") 46 | XCTAssert(white2.toHex() == 0xffffff, "Color should be white") 47 | 48 | XCTAssert(red.isEqual(toHexString: DynamicColor.red.toHexString()), "Color should be red") 49 | XCTAssert(green.isEqual(toHexString: DynamicColor.green.toHexString()), "Color should be green") 50 | XCTAssert(blue.isEqual(toHexString: DynamicColor.blue.toHexString()), "Color should be blue") 51 | XCTAssert(custom.isEqual(toHexString: "#e74d3c"), "Color should be equal to #e74d3c") 52 | } 53 | 54 | func testToHSLComponents() { 55 | let customColor = DynamicColor(hue: 6, saturation: 0.781, lightness: 0.571) 56 | let hsl = customColor.toHSLComponents() 57 | 58 | XCTAssertEqual(hsl.h, 6, accuracy: TestsAcceptedAccuracy, "Color hue component should be equal to 6° (not \(hsl.h))") 59 | XCTAssertEqual(hsl.s, 0.781, accuracy: TestsAcceptedAccuracy, "Saturation component should be equal to 0.781 (not \(hsl.s))") 60 | XCTAssertEqual(hsl.l, 0.571, accuracy: TestsAcceptedAccuracy, "Lightness component should be equal to 0.571 (not \(hsl.l))") 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Tests/DynamicColor+RGBATests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import XCTest 28 | @testable import DynamicColor 29 | 30 | class DynamicColorRGBATests: XCTestCase { 31 | func testInit() { 32 | let customInitColor = DynamicColor(r: 58.65, g: 117.3, b: 81.6) 33 | let customColor = DynamicColor(red: 0.23, green: 0.46, blue: 0.32, alpha: 1) 34 | 35 | XCTAssertEqual(customInitColor.toHex(), customColor.toHex()) 36 | } 37 | 38 | func testToRGBAComponents() { 39 | let rgbaColor = DynamicColor(red: 0.23, green: 0.46, blue: 0.32, alpha: 1) 40 | let rgba1 = rgbaColor.toRGBAComponents() 41 | XCTAssertEqual(rgba1.r, 0.23) 42 | XCTAssertEqual(rgba1.g, 0.46) 43 | XCTAssertEqual(rgba1.b, 0.32) 44 | XCTAssertEqual(rgba1.a, 1.00) 45 | 46 | let grayscaleColor = DynamicColor(white: 0.42, alpha: 1) 47 | let rgba2 = grayscaleColor.toRGBAComponents() 48 | XCTAssertEqual(rgba2.r, 0.42, accuracy: 0.001) 49 | XCTAssertEqual(rgba2.g, 0.42, accuracy: 0.001) 50 | XCTAssertEqual(rgba2.b, 0.42, accuracy: 0.001) 51 | XCTAssertEqual(rgba2.a, 1.00, accuracy: 0.001) 52 | } 53 | 54 | func testRedComponent() { 55 | let customColor = DynamicColor(red: 0.23, green: 0.46, blue: 0.32, alpha: 1) 56 | 57 | let redComponent = customColor.redComponent 58 | 59 | XCTAssert(redComponent == 0.23, "Color red component should be equal to 0.23") 60 | } 61 | 62 | func testGreenComponent() { 63 | let customColor = DynamicColor(red: 0.23, green: 0.46, blue: 0.32, alpha: 1) 64 | 65 | let greenComponent = customColor.greenComponent 66 | 67 | XCTAssert(greenComponent == 0.46, "Color green component should be equal to 0.46") 68 | } 69 | 70 | func testBlueComponent() { 71 | let customColor = DynamicColor(red: 0.23, green: 0.46, blue: 0.32, alpha: 1) 72 | 73 | let blueComponent = customColor.blueComponent 74 | 75 | XCTAssert(blueComponent == 0.32, "Color blue component should be equal to 0.32") 76 | } 77 | 78 | func testAlphaComponent() { 79 | let customColor = DynamicColor(red: 0.23, green: 0.46, blue: 0.32, alpha: 0.8) 80 | 81 | let alphaComponent = customColor.alphaComponent 82 | 83 | XCTAssert(alphaComponent == 0.8, "Color alpha component should be equal to 0.8") 84 | } 85 | 86 | func testAdjustedAlphaColor() { 87 | let customColor = DynamicColor(red: 0.23, green: 0.46, blue: 0.32, alpha: 1) 88 | 89 | XCTAssert(customColor.alphaComponent == 1, "Color alpha component should be equal to 1") 90 | 91 | let adjustedAlpha1 = customColor.adjustedAlpha(amount: -0.5) 92 | 93 | XCTAssert(adjustedAlpha1.alphaComponent == 0.5, "Color alpha component should be equal to 0.5") 94 | 95 | let adjustedAlpha2 = adjustedAlpha1.adjustedAlpha(amount: 0.2) 96 | 97 | XCTAssert(adjustedAlpha2.alphaComponent == 0.7, "Color alpha component should be equal to 0.7") 98 | 99 | let adjustedAlpha3 = adjustedAlpha2.adjustedAlpha(amount: -1) 100 | 101 | XCTAssert(adjustedAlpha3.alphaComponent == 0, "Color alpha component should be equal to 0") 102 | 103 | let adjustedAlpha4 = adjustedAlpha3.adjustedAlpha(amount: 23) 104 | 105 | XCTAssert(adjustedAlpha4.alphaComponent == 1, "Color alpha component should be equal to 1") 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Tests/DynamicColor+XCTAssertEqual.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import XCTest 28 | @testable import DynamicColor 29 | 30 | func XCTAssertEqual(_ expression1: @autoclosure () throws -> DynamicColor, _ expression2: @autoclosure () throws -> DynamicColor, accuracy: CGFloat, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) { 31 | 32 | let message = message() 33 | 34 | do { 35 | let (color1, color2) = (try expression1(), try expression2()) 36 | 37 | let (r1, g1, b1, a1) = color1.toRGBAComponents() 38 | let (r2, g2, b2, a2) = color2.toRGBAComponents() 39 | 40 | XCTAssertEqual(r1, r2, accuracy: accuracy, message, file: file, line: line) 41 | XCTAssertEqual(g1, g2, accuracy: accuracy, message, file: file, line: line) 42 | XCTAssertEqual(b1, b2, accuracy: accuracy, message, file: file, line: line) 43 | XCTAssertEqual(a1, a2, accuracy: accuracy, message, file: file, line: line) 44 | } catch let error { 45 | XCTFail("\(error)", file: file, line: line) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Tests/DynamicColor+XYZTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import XCTest 28 | @testable import DynamicColor 29 | 30 | class DynamicColorXYZTests: XCTestCase { 31 | func testInitWithXYZComponents() { 32 | let whiteColor = DynamicColor(X: 95.05, Y: 100, Z: 108.9).toRGBAComponents() 33 | XCTAssert(whiteColor.r == 1, "Red component should be equal to 1 (not \(whiteColor.r))") 34 | XCTAssert(whiteColor.g == 1, "Green component should be equal to 1 (not \(whiteColor.g))") 35 | XCTAssert(whiteColor.b == 1, "Blue component should be equal to 1 (not \(whiteColor.b))") 36 | 37 | let blackColor = DynamicColor(X: 0, Y: 0, Z: 0).toRGBAComponents() 38 | XCTAssert(blackColor.r == 0, "Red component should be equal to 0 (not \(blackColor.r))") 39 | XCTAssert(blackColor.g == 0, "Green component should be equal to 0 (not \(blackColor.g))") 40 | XCTAssert(blackColor.b == 0, "Blue component should be equal to 0 (not \(blackColor.b))") 41 | 42 | let blueColor = DynamicColor(X: 18.05, Y: 7.22, Z: 95.05).toRGBAComponents() 43 | XCTAssert(blueColor.r == 0, "Red component should be equal to 0 (not \(blueColor.r))") 44 | XCTAssert(blueColor.g == 0, "Green component should be equal to 0 (not \(blueColor.g))") 45 | XCTAssert(blueColor.b == 1, "Blue component should be equal to 1 (not \(blueColor.b))") 46 | 47 | let customColor = DynamicColor(X: 37.177, Y: 46.108, Z: 10.189).toRGBAComponents() 48 | XCTAssert(customColor.r == 0.698, "Red component should be equal to 0.698 (not \(customColor.r))") 49 | XCTAssert(customColor.g == 0.741, "Green component should be equal to 0.741 (not \(customColor.g))") 50 | XCTAssert(customColor.b == 0.204, "Blue component should be equal to 0.204 (not \(customColor.b))") 51 | } 52 | 53 | func testToHSLComponents() { 54 | let whiteXYZ = DynamicColor.white.toXYZComponents() 55 | XCTAssert(whiteXYZ.X == 95.05, "X component should be equal to 95.05 (not \(whiteXYZ.X))") 56 | XCTAssert(whiteXYZ.Y == 100, "Y component should be equal to 100 (not \(whiteXYZ.Y))") 57 | XCTAssert(whiteXYZ.Z == 108.9, "Z component should be equal to 108.9 (not \(whiteXYZ.Z))") 58 | 59 | let blackXYZ = DynamicColor.black.toXYZComponents() 60 | XCTAssert(blackXYZ.X == 0, "X component should be equal to 0 (not \(blackXYZ.X))") 61 | XCTAssert(blackXYZ.Y == 0, "Y component should be equal to 0 (not \(blackXYZ.Y))") 62 | XCTAssert(blackXYZ.Z == 0, "Z component should be equal to 0 (not \(blackXYZ.Z))") 63 | 64 | let blueXYZ = DynamicColor.blue.toXYZComponents() 65 | XCTAssert(blueXYZ.X == 18.05, "X component should be equal to 18.05 (not \(blueXYZ.X))") 66 | XCTAssert(blueXYZ.Y == 7.22, "Y component should be equal to 7.22 (not \(blueXYZ.Y))") 67 | XCTAssert(blueXYZ.Z == 95.05, "Z component should be equal to 95.05 (not \(blueXYZ.Z))") 68 | 69 | let customXYZ = DynamicColor(red: 0.69804, green: 0.74118, blue: 0.20392, alpha: 1).toXYZComponents() 70 | XCTAssert(customXYZ.X == 37.178, "X component should be equal to 37.178 (not \(customXYZ.X))") 71 | XCTAssert(customXYZ.Y == 46.109, "Y component should be equal to 46.109 (not \(customXYZ.Y))") 72 | XCTAssert(customXYZ.Z == 10.189, "Z component should be equal to 10.189 (not \(customXYZ.Z))") 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Tests/DynamicColorArrayTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import XCTest 28 | @testable import DynamicColor 29 | 30 | class DynamicColorArrayTests: XCTestCase { 31 | func testGradientProperty() { 32 | let colors: [DynamicColor] = [] 33 | 34 | XCTAssertNotNil(colors.gradient) 35 | } 36 | 37 | func testColors() { 38 | let colors = [#colorLiteral(red: 1, green: 0, blue: 0, alpha: 1), #colorLiteral(red: 0, green: 1, blue: 0, alpha: 1), #colorLiteral(red: 0, green: 0, blue: 1, alpha: 1)].gradient.colorPalette(amount: 5) 39 | 40 | XCTAssert(colors[0].isEqual(#colorLiteral(red: 1, green: 0, blue: 0, alpha: 1)), "Should be red") 41 | XCTAssert(colors[1].isEqual(DynamicColor(red: 0.5, green: 0.5, blue: 0, alpha: 1)), "Should be kaki") 42 | XCTAssert(colors[2].isEqual(#colorLiteral(red: 0, green: 1, blue: 0, alpha: 1)), "Should be green") 43 | XCTAssert(colors[3].isEqual(DynamicColor(red: 0, green: 0.5, blue: 0.5, alpha: 1)), "Should be purple") 44 | XCTAssert(colors[4].isEqual(#colorLiteral(red: 0, green: 0, blue: 1, alpha: 1)), "Should be blue") 45 | } 46 | 47 | func testColorAt() { 48 | let emptyColors = Array().gradient 49 | 50 | XCTAssert(emptyColors.pickColorAt(scale: 0.25).isEqual(toHex: 0x00000), "Should be black") 51 | 52 | let oneColor = [#colorLiteral(red: 1, green: 0, blue: 0, alpha: 1)].gradient 53 | 54 | XCTAssert(oneColor.pickColorAt(scale: 0.75).isEqual(#colorLiteral(red: 1, green: 0, blue: 0, alpha: 1)), "Should be red") 55 | 56 | let primaryColors = [#colorLiteral(red: 1, green: 0, blue: 0, alpha: 1), #colorLiteral(red: 0, green: 1, blue: 0, alpha: 1), #colorLiteral(red: 0, green: 0, blue: 1, alpha: 1)].gradient 57 | 58 | let red = primaryColors.pickColorAt(scale: 0) 59 | XCTAssert(red.isEqual(#colorLiteral(red: 1, green: 0, blue: 0, alpha: 1)), "Should be red") 60 | 61 | let clippedRed = primaryColors.pickColorAt(scale: -7.9) 62 | XCTAssert(clippedRed.isEqual(#colorLiteral(red: 1, green: 0, blue: 0, alpha: 1)), "Should be red") 63 | 64 | let blue = primaryColors.pickColorAt(scale: 1) 65 | XCTAssert(blue.isEqual(#colorLiteral(red: 0, green: 0, blue: 1, alpha: 1)), "Should be blue") 66 | 67 | let clippedBlue = primaryColors.pickColorAt(scale: 34) 68 | XCTAssert(clippedBlue.isEqual(#colorLiteral(red: 0, green: 0, blue: 1, alpha: 1)), "Should be blue") 69 | 70 | let kaki = primaryColors.pickColorAt(scale: 0.25) 71 | XCTAssert(kaki.toRGBAComponents() == (r: 0.5, g: 0.5, b: 0, a: 1), "Should be kaki (not \(kaki))") 72 | 73 | let green = primaryColors.pickColorAt(scale: 0.65) 74 | XCTAssert(green.toRGBAComponents().r == 0, "Should be green (not \(green.toRGBAComponents()))") 75 | XCTAssert(green.toRGBAComponents().g == 0.7, "Should be green (not \(green.toRGBAComponents()))") 76 | XCTAssert(round(green.toRGBAComponents().b * 10) == 3, "Should be green (not \(green.toRGBAComponents().b))") 77 | XCTAssert(green.toRGBAComponents().a == 1, "Should be green (not \(green.toRGBAComponents()))") 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Tests/DynamicColorLabTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import XCTest 28 | @testable import DynamicColor 29 | 30 | class DynamicColorLabTests: XCTestCase { 31 | func testInitWithLabComponents() { 32 | let whiteColor = DynamicColor(L: 100, a: 0, b: 0).toRGBAComponents() 33 | XCTAssert(whiteColor.r == 1, "Red component should be equal to 1 (not \(whiteColor.r))") 34 | XCTAssert(whiteColor.g == 1, "Green component should be equal to 1 (not \(whiteColor.g))") 35 | XCTAssert(whiteColor.b == 1, "Blue component should be equal to 1 (not \(whiteColor.b))") 36 | 37 | let blackColor = DynamicColor(L: 0, a: 0, b: 0).toRGBAComponents() 38 | XCTAssert(blackColor.r == 0, "Red component should be equal to 0 (not \(blackColor.r))") 39 | XCTAssert(blackColor.g == 0, "Green component should be equal to 0 (not \(blackColor.g))") 40 | XCTAssert(blackColor.b == 0, "Blue component should be equal to 0 (not \(blackColor.b))") 41 | 42 | let redColor = DynamicColor(L: 53.23, a: 80.10, b: 67.22).toRGBAComponents() 43 | XCTAssert(redColor.r == 1, "Red component should be equal to 1 (not \(redColor.r))") 44 | XCTAssert(redColor.g == 0, "Green component should be equal to 0 (not \(redColor.g))") 45 | XCTAssert(redColor.b == 0, "Blue component should be equal to 0 (not \(redColor.b))") 46 | 47 | let yellowColor = DynamicColor(L: 97.138, a: -21.56, b: 94.487).toRGBAComponents() 48 | XCTAssert(yellowColor.r == 1, "L component should be equal to 1 (not \(yellowColor.r))") 49 | XCTAssert(yellowColor.g == 1, "a component should be equal to 1 (not \(yellowColor.g))") 50 | XCTAssert(yellowColor.b == 0, "b component should be equal to 0 (not \(yellowColor.b))") 51 | 52 | let customColor = DynamicColor(L: 50.493, a: -49.333, b: 31.056).toRGBAComponents() 53 | XCTAssert(customColor.r == 0, "Red component should be equal to 0 (not \(customColor.r))") 54 | XCTAssert(customColor.g == 0.545, "Green component should be equal to 0.545 (not \(customColor.g))") 55 | XCTAssert(customColor.b == 0.251, "Blue component should be equal to 0.251 (not \(customColor.b))") 56 | } 57 | 58 | func testToLabComponents() { 59 | let whiteLab = DynamicColor.white.toLabComponents() 60 | XCTAssert(whiteLab.L == 100, "L component should be equal to 100 (not \(whiteLab.L))") 61 | XCTAssert(whiteLab.a == 0, "a component should be equal to 0 (not \(whiteLab.a))") 62 | XCTAssert(whiteLab.b == 0, "b component should be equal to 0 (not \(whiteLab.b))") 63 | 64 | let blackLab = DynamicColor.black.toLabComponents() 65 | XCTAssert(blackLab.L == 0, "L component should be equal to 0 (not \(blackLab.L))") 66 | XCTAssert(blackLab.a == 0, "a component should be equal to 0 (not \(blackLab.a))") 67 | XCTAssert(blackLab.b == 0, "b component should be equal to 0 (not \(blackLab.b))") 68 | 69 | let redLab = DynamicColor.red.toLabComponents() 70 | XCTAssert(redLab.L == 53.233, "L component should be equal to 53.233 (not \(redLab.L))") 71 | XCTAssert(redLab.a == 80.105, "a component should be equal to 80.105 (not \(redLab.a))") 72 | XCTAssert(redLab.b == 67.223, "b component should be equal to 67.223 (not \(redLab.b))") 73 | 74 | let yellowLab = DynamicColor.yellow.toLabComponents() 75 | XCTAssert(yellowLab.L == 97.138, "L component should be equal to 97.138 (not \(yellowLab.L))") 76 | XCTAssert(yellowLab.a == -21.561, "a component should be equal to -21.561 (not \(yellowLab.a))") 77 | XCTAssert(yellowLab.b == 94.488, "b component should be equal to 94.488 (not \(yellowLab.b))") 78 | 79 | let maxLab = DynamicColor(hex: 0xFF4500).toLabComponents() 80 | XCTAssert(maxLab.L == 57.575, "L component should be equal to 57.575 (not \(maxLab.L))") 81 | XCTAssert(maxLab.a == 67.792, "a component should be equal to 67.792 (not \(maxLab.a))") 82 | XCTAssert(maxLab.b == 68.977, "b component should be equal to 68.977 (not \(maxLab.b))") 83 | 84 | let minLab = DynamicColor(hex: 0x008B40).toLabComponents() 85 | XCTAssert(minLab.L == 50.494, "L component should be equal to 50.494 (not \(minLab.L))") 86 | XCTAssert(minLab.a == -49.334, "a component should be equal to -49.334 (not \(minLab.a))") 87 | XCTAssert(minLab.b == 31.053, "b component should be equal to 31.053 (not \(minLab.b))") 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Tests/DynamicGradientTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicColor 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import XCTest 28 | @testable import DynamicColor 29 | 30 | class DynamicGradientTests: XCTestCase { 31 | func testColorPalette() { 32 | XCTAssertEqual(DynamicGradient(colors: []).colorPalette(amount: 0).count, 0) 33 | XCTAssertEqual(DynamicGradient(colors: []).colorPalette(amount: 99).count, 0) 34 | XCTAssertEqual(DynamicGradient(colors: [.red, .green, .blue]).colorPalette(amount: 0).count, 0) 35 | XCTAssertEqual(DynamicGradient(colors: [.red]).colorPalette(amount: 10).count, 10) 36 | XCTAssertEqual(DynamicGradient(colors: [.red]).colorPalette(amount: 10), (0 ..< 10).map({ _ in return .red })) 37 | } 38 | 39 | func testPickColorAtScale() { 40 | let yellow = [.red, .yellow, .green].gradient.pickColorAt(scale: 0.5) 41 | 42 | XCTAssert(yellow.toHex() == 0xffff00, "Color should be yellow (not \(yellow.toHexString()))") 43 | 44 | let red = [.red, .yellow, .green].gradient.pickColorAt(scale: 0) 45 | 46 | XCTAssert(red.toHex() == 0xff0000, "Color should be red (not \(red.toHexString()))") 47 | 48 | let green = [.red, .yellow, .green].gradient.pickColorAt(scale: 1) 49 | 50 | XCTAssert(green.toHex() == 0x00ff00, "Color should be green (not \(green.toHexString()))") 51 | 52 | let darkYellow = [.red, .green].gradient.pickColorAt(scale: 0.5) 53 | 54 | XCTAssert(darkYellow.toHex() == 0x808000, "Color should be a dark yellow (not \(darkYellow.toHexString()))") 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | ignore: 3 | - Examples/* 4 | - Tests/* 5 | --------------------------------------------------------------------------------