├── .gitignore ├── .swift-version ├── .travis.yml ├── Example ├── Example │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── bush.imageset │ │ │ ├── 440px-George-W-Bush.jpeg │ │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ ├── SceneDelegate.swift │ └── ViewController.swift ├── Podfile ├── Tests │ ├── Info.plist │ └── Tests.swift └── swiftVibrant.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ └── contents.xcworkspacedata │ └── xcshareddata │ └── xcschemes │ └── swiftVibrant-Example.xcscheme ├── LICENSE ├── README.md ├── _Pods.xcodeproj ├── swift-vibrant.podspec └── swiftVibrant ├── Builder.swift ├── ColorConverters.swift ├── Constants.swift ├── Filter.swift ├── Generator.swift ├── Image.swift ├── MMCQ.swift ├── Quantizer.swift ├── Util.swift ├── Vbox.swift ├── Vibrant.swift └── VibrantColors.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata/ 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 26 | # Carthage/Checkouts 27 | 28 | Carthage/Build 29 | 30 | # We recommend against adding the Pods directory to your .gitignore. However 31 | # you should judge for yourself, the pros and cons are mentioned at: 32 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 33 | # 34 | # Note: if you ignore the Pods directory, make sure to uncomment 35 | # `pod install` in .travis.yml 36 | # 37 | # Pods/ 38 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * https://www.objc.io/issues/6-build-tools/travis-ci/ 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | osx_image: xcode7.3 6 | language: objective-c 7 | # cache: cocoapods 8 | # podfile: Example/Podfile 9 | # before_install: 10 | # - gem install cocoapods # Since Travis is not always on latest version 11 | # - pod install --project-directory=Example 12 | script: 13 | - set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/swiftVibrant.xcworkspace -scheme swiftVibrant-Example -sdk iphonesimulator9.3 ONLY_ACTIVE_ARCH=NO | xcpretty 14 | - pod lib lint 15 | -------------------------------------------------------------------------------- /Example/Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Example 4 | // 5 | // Created by Bryce Dougherty on 5/3/20. 6 | // Copyright © 2020 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /Example/Example/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 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/bush.imageset/440px-George-W-Bush.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bd452/swift-vibrant/e7e10934ed5c2ee6a290b4bc8c0acab00db8c25a/Example/Example/Assets.xcassets/bush.imageset/440px-George-W-Bush.jpeg -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/bush.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "440px-George-W-Bush.jpeg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Example/Example/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 | -------------------------------------------------------------------------------- /Example/Example/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 | -------------------------------------------------------------------------------- /Example/Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIMainStoryboardFile 45 | Main 46 | UIRequiredDeviceCapabilities 47 | 48 | armv7 49 | 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | UISupportedInterfaceOrientations~ipad 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationPortraitUpsideDown 60 | UIInterfaceOrientationLandscapeLeft 61 | UIInterfaceOrientationLandscapeRight 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /Example/Example/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Example 4 | // 5 | // Created by Bryce Dougherty on 5/3/20. 6 | // Copyright © 2020 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | 16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 20 | guard let _ = (scene as? UIWindowScene) else { return } 21 | } 22 | 23 | func sceneDidDisconnect(_ scene: UIScene) { 24 | // Called as the scene is being released by the system. 25 | // This occurs shortly after the scene enters the background, or when its session is discarded. 26 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 27 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 28 | } 29 | 30 | func sceneDidBecomeActive(_ scene: UIScene) { 31 | // Called when the scene has moved from an inactive state to an active state. 32 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 33 | } 34 | 35 | func sceneWillResignActive(_ scene: UIScene) { 36 | // Called when the scene will move from an active state to an inactive state. 37 | // This may occur due to temporary interruptions (ex. an incoming phone call). 38 | } 39 | 40 | func sceneWillEnterForeground(_ scene: UIScene) { 41 | // Called as the scene transitions from the background to the foreground. 42 | // Use this method to undo the changes made on entering the background. 43 | } 44 | 45 | func sceneDidEnterBackground(_ scene: UIScene) { 46 | // Called as the scene transitions from the foreground to the background. 47 | // Use this method to save data, release shared resources, and store enough scene-specific state information 48 | // to restore the scene back to its current state. 49 | } 50 | 51 | 52 | } 53 | 54 | -------------------------------------------------------------------------------- /Example/Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Example 4 | // 5 | // Created by Bryce Dougherty on 5/3/20. 6 | // Copyright © 2020 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import swiftVibrant 11 | 12 | class ViewController: UIViewController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | let vibrantView = UIView(frame: CGRect(x: 0, y: 100, width: 50, height: 50)) 17 | let darkVibrant = UIView(frame: CGRect(x: 50, y: 100, width: 50, height: 50)) 18 | let lightVibrant = UIView(frame: CGRect(x: 100, y: 100, width: 50, height: 50)) 19 | let muted = UIView(frame: CGRect(x: 150, y: 100, width: 50, height: 50)) 20 | let lightmuted = UIView(frame: CGRect(x: 200, y: 100, width: 50, height: 50)) 21 | let darkMuted = UIView(frame: CGRect(x: 250, y: 100, width: 50, height: 50)) 22 | self.view.addSubview(vibrantView) 23 | self.view.addSubview(darkVibrant) 24 | self.view.addSubview(lightVibrant) 25 | self.view.addSubview(muted) 26 | self.view.addSubview(lightmuted) 27 | self.view.addSubview(darkMuted) 28 | super.viewDidLoad() 29 | 30 | 31 | vibrantView.backgroundColor = UIColor.white 32 | darkVibrant.backgroundColor = UIColor.white 33 | lightVibrant.backgroundColor = UIColor.white 34 | muted.backgroundColor = UIColor.white 35 | lightmuted.backgroundColor = UIColor.white 36 | darkMuted.backgroundColor = UIColor.white 37 | 38 | let img = UIImage(named: "bush")! 39 | 40 | Vibrant.from(img).maxDimension(100).getPalette { palette in 41 | vibrantView.backgroundColor = palette.Vibrant?.uiColor 42 | darkVibrant.backgroundColor = palette.DarkVibrant?.uiColor 43 | lightVibrant.backgroundColor = palette.LightVibrant?.uiColor 44 | muted.backgroundColor = palette.Muted?.uiColor 45 | lightmuted.backgroundColor = palette.LightMuted?.uiColor 46 | darkMuted.backgroundColor = palette.DarkMuted?.uiColor 47 | } 48 | 49 | 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | target 'swiftVibrant_Tests' do 3 | pod 'swift-vibrant', :path => '../' 4 | end 5 | target 'Example' do 6 | pod 'swift-vibrant', :path => '../' 7 | end 8 | -------------------------------------------------------------------------------- /Example/Tests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Tests/Tests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import swiftVibrant 3 | 4 | class Tests: XCTestCase { 5 | 6 | override func setUp() { 7 | super.setUp() 8 | // Put setup code here. This method is called before the invocation of each test method in the class. 9 | } 10 | 11 | override func tearDown() { 12 | // Put teardown code here. This method is called after the invocation of each test method in the class. 13 | super.tearDown() 14 | } 15 | 16 | func testGetColors() { 17 | 18 | } 19 | 20 | func testExample() { 21 | // This is an example of a functional test case. 22 | XCTAssert(true, "Pass") 23 | } 24 | 25 | func testPerformanceExample() { 26 | // This is an example of a performance test case. 27 | self.measure() { 28 | // Put the code you want to measure the time of here. 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Example/swiftVibrant.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 02CE3D59245EF19700E786F8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CE3D58245EF19700E786F8 /* AppDelegate.swift */; }; 11 | 02CE3D5B245EF19700E786F8 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CE3D5A245EF19700E786F8 /* SceneDelegate.swift */; }; 12 | 02CE3D5D245EF19700E786F8 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CE3D5C245EF19700E786F8 /* ViewController.swift */; }; 13 | 02CE3D60245EF19700E786F8 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 02CE3D5E245EF19700E786F8 /* Main.storyboard */; }; 14 | 02CE3D62245EF19900E786F8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 02CE3D61245EF19900E786F8 /* Assets.xcassets */; }; 15 | 02CE3D65245EF19900E786F8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 02CE3D63245EF19900E786F8 /* LaunchScreen.storyboard */; }; 16 | 3BD5974AD22A1E555E8FBF93 /* Pods_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F18D8FE66FC0E27DDB20C53E /* Pods_Example.framework */; }; 17 | 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* Tests.swift */; }; 18 | CBE417F370B6DE8A27E191C0 /* Pods_swiftVibrant_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCA06F1DE6F9C077DEB3AF1E /* Pods_swiftVibrant_Tests.framework */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 02CE3D56245EF19700E786F8 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | 02CE3D58245EF19700E786F8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 24 | 02CE3D5A245EF19700E786F8 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 25 | 02CE3D5C245EF19700E786F8 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 26 | 02CE3D5F245EF19700E786F8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 27 | 02CE3D61245EF19900E786F8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 28 | 02CE3D64245EF19900E786F8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 29 | 02CE3D66245EF19900E786F8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 30 | 04B2C6316ED3FEA12D2E413E /* Pods-swiftVibrant_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-swiftVibrant_Tests.release.xcconfig"; path = "Target Support Files/Pods-swiftVibrant_Tests/Pods-swiftVibrant_Tests.release.xcconfig"; sourceTree = ""; }; 31 | 04DDE9579463834E92864267 /* Pods-Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.debug.xcconfig"; path = "Target Support Files/Pods-Example/Pods-Example.debug.xcconfig"; sourceTree = ""; }; 32 | 1BF125B0EC659770D6757116 /* Pods-swiftVibrant_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-swiftVibrant_Tests.debug.xcconfig"; path = "Target Support Files/Pods-swiftVibrant_Tests/Pods-swiftVibrant_Tests.debug.xcconfig"; sourceTree = ""; }; 33 | 607FACE51AFB9204008FA782 /* swiftVibrant_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = swiftVibrant_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 35 | 607FACEB1AFB9204008FA782 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = ""; }; 36 | 631AEB3044C059FA9826ECDF /* Pods-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.release.xcconfig"; path = "Target Support Files/Pods-Example/Pods-Example.release.xcconfig"; sourceTree = ""; }; 37 | C12DA4ED3ADB9413945322DE /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 38 | D77DF502E212097128D250B4 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 39 | DCA06F1DE6F9C077DEB3AF1E /* Pods_swiftVibrant_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_swiftVibrant_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | EC2887334F293C46996DAFD8 /* swiftVibrant.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = swiftVibrant.podspec; path = ../swiftVibrant.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 41 | F18D8FE66FC0E27DDB20C53E /* Pods_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | /* End PBXFileReference section */ 43 | 44 | /* Begin PBXFrameworksBuildPhase section */ 45 | 02CE3D53245EF19700E786F8 /* Frameworks */ = { 46 | isa = PBXFrameworksBuildPhase; 47 | buildActionMask = 2147483647; 48 | files = ( 49 | 3BD5974AD22A1E555E8FBF93 /* Pods_Example.framework in Frameworks */, 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | 607FACE21AFB9204008FA782 /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | CBE417F370B6DE8A27E191C0 /* Pods_swiftVibrant_Tests.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 02CE3D57245EF19700E786F8 /* Example */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 02CE3D58245EF19700E786F8 /* AppDelegate.swift */, 68 | 02CE3D5A245EF19700E786F8 /* SceneDelegate.swift */, 69 | 02CE3D5C245EF19700E786F8 /* ViewController.swift */, 70 | 02CE3D5E245EF19700E786F8 /* Main.storyboard */, 71 | 02CE3D61245EF19900E786F8 /* Assets.xcassets */, 72 | 02CE3D63245EF19900E786F8 /* LaunchScreen.storyboard */, 73 | 02CE3D66245EF19900E786F8 /* Info.plist */, 74 | ); 75 | path = Example; 76 | sourceTree = ""; 77 | }; 78 | 582F4894A1C95F6C9057F557 /* Frameworks */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | DCA06F1DE6F9C077DEB3AF1E /* Pods_swiftVibrant_Tests.framework */, 82 | F18D8FE66FC0E27DDB20C53E /* Pods_Example.framework */, 83 | ); 84 | name = Frameworks; 85 | sourceTree = ""; 86 | }; 87 | 607FACC71AFB9204008FA782 = { 88 | isa = PBXGroup; 89 | children = ( 90 | 607FACF51AFB993E008FA782 /* Podspec Metadata */, 91 | 607FACE81AFB9204008FA782 /* Tests */, 92 | 02CE3D57245EF19700E786F8 /* Example */, 93 | 607FACD11AFB9204008FA782 /* Products */, 94 | 694AD21C6B21895C373D1D37 /* Pods */, 95 | 582F4894A1C95F6C9057F557 /* Frameworks */, 96 | ); 97 | sourceTree = ""; 98 | }; 99 | 607FACD11AFB9204008FA782 /* Products */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 607FACE51AFB9204008FA782 /* swiftVibrant_Tests.xctest */, 103 | 02CE3D56245EF19700E786F8 /* Example.app */, 104 | ); 105 | name = Products; 106 | sourceTree = ""; 107 | }; 108 | 607FACE81AFB9204008FA782 /* Tests */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 607FACEB1AFB9204008FA782 /* Tests.swift */, 112 | 607FACE91AFB9204008FA782 /* Supporting Files */, 113 | ); 114 | path = Tests; 115 | sourceTree = ""; 116 | }; 117 | 607FACE91AFB9204008FA782 /* Supporting Files */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 607FACEA1AFB9204008FA782 /* Info.plist */, 121 | ); 122 | name = "Supporting Files"; 123 | sourceTree = ""; 124 | }; 125 | 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | EC2887334F293C46996DAFD8 /* swiftVibrant.podspec */, 129 | D77DF502E212097128D250B4 /* README.md */, 130 | C12DA4ED3ADB9413945322DE /* LICENSE */, 131 | ); 132 | name = "Podspec Metadata"; 133 | sourceTree = ""; 134 | }; 135 | 694AD21C6B21895C373D1D37 /* Pods */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | 1BF125B0EC659770D6757116 /* Pods-swiftVibrant_Tests.debug.xcconfig */, 139 | 04B2C6316ED3FEA12D2E413E /* Pods-swiftVibrant_Tests.release.xcconfig */, 140 | 04DDE9579463834E92864267 /* Pods-Example.debug.xcconfig */, 141 | 631AEB3044C059FA9826ECDF /* Pods-Example.release.xcconfig */, 142 | ); 143 | path = Pods; 144 | sourceTree = ""; 145 | }; 146 | /* End PBXGroup section */ 147 | 148 | /* Begin PBXNativeTarget section */ 149 | 02CE3D55245EF19700E786F8 /* Example */ = { 150 | isa = PBXNativeTarget; 151 | buildConfigurationList = 02CE3D67245EF19900E786F8 /* Build configuration list for PBXNativeTarget "Example" */; 152 | buildPhases = ( 153 | 02E82B16C38838BFAAE5AD04 /* [CP] Check Pods Manifest.lock */, 154 | 02CE3D52245EF19700E786F8 /* Sources */, 155 | 02CE3D53245EF19700E786F8 /* Frameworks */, 156 | 02CE3D54245EF19700E786F8 /* Resources */, 157 | E35689F2C1DB016A71102447 /* [CP] Embed Pods Frameworks */, 158 | ); 159 | buildRules = ( 160 | ); 161 | dependencies = ( 162 | ); 163 | name = Example; 164 | productName = Example; 165 | productReference = 02CE3D56245EF19700E786F8 /* Example.app */; 166 | productType = "com.apple.product-type.application"; 167 | }; 168 | 607FACE41AFB9204008FA782 /* swiftVibrant_Tests */ = { 169 | isa = PBXNativeTarget; 170 | buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "swiftVibrant_Tests" */; 171 | buildPhases = ( 172 | C1CB5EA0B6D4E1478AF9E75C /* [CP] Check Pods Manifest.lock */, 173 | 607FACE11AFB9204008FA782 /* Sources */, 174 | 607FACE21AFB9204008FA782 /* Frameworks */, 175 | 607FACE31AFB9204008FA782 /* Resources */, 176 | B29FFF59C84F8D2B1B7169E3 /* [CP] Embed Pods Frameworks */, 177 | ); 178 | buildRules = ( 179 | ); 180 | dependencies = ( 181 | ); 182 | name = swiftVibrant_Tests; 183 | productName = Tests; 184 | productReference = 607FACE51AFB9204008FA782 /* swiftVibrant_Tests.xctest */; 185 | productType = "com.apple.product-type.bundle.unit-test"; 186 | }; 187 | /* End PBXNativeTarget section */ 188 | 189 | /* Begin PBXProject section */ 190 | 607FACC81AFB9204008FA782 /* Project object */ = { 191 | isa = PBXProject; 192 | attributes = { 193 | LastSwiftUpdateCheck = 1140; 194 | LastUpgradeCheck = 0830; 195 | ORGANIZATIONNAME = CocoaPods; 196 | TargetAttributes = { 197 | 02CE3D55245EF19700E786F8 = { 198 | CreatedOnToolsVersion = 11.4.1; 199 | DevelopmentTeam = GDNLAH489Z; 200 | ProvisioningStyle = Automatic; 201 | }; 202 | 607FACE41AFB9204008FA782 = { 203 | CreatedOnToolsVersion = 6.3.1; 204 | DevelopmentTeam = GDNLAH489Z; 205 | LastSwiftMigration = 0900; 206 | }; 207 | }; 208 | }; 209 | buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "swiftVibrant" */; 210 | compatibilityVersion = "Xcode 3.2"; 211 | developmentRegion = English; 212 | hasScannedForEncodings = 0; 213 | knownRegions = ( 214 | English, 215 | en, 216 | Base, 217 | ); 218 | mainGroup = 607FACC71AFB9204008FA782; 219 | productRefGroup = 607FACD11AFB9204008FA782 /* Products */; 220 | projectDirPath = ""; 221 | projectRoot = ""; 222 | targets = ( 223 | 607FACE41AFB9204008FA782 /* swiftVibrant_Tests */, 224 | 02CE3D55245EF19700E786F8 /* Example */, 225 | ); 226 | }; 227 | /* End PBXProject section */ 228 | 229 | /* Begin PBXResourcesBuildPhase section */ 230 | 02CE3D54245EF19700E786F8 /* Resources */ = { 231 | isa = PBXResourcesBuildPhase; 232 | buildActionMask = 2147483647; 233 | files = ( 234 | 02CE3D65245EF19900E786F8 /* LaunchScreen.storyboard in Resources */, 235 | 02CE3D62245EF19900E786F8 /* Assets.xcassets in Resources */, 236 | 02CE3D60245EF19700E786F8 /* Main.storyboard in Resources */, 237 | ); 238 | runOnlyForDeploymentPostprocessing = 0; 239 | }; 240 | 607FACE31AFB9204008FA782 /* Resources */ = { 241 | isa = PBXResourcesBuildPhase; 242 | buildActionMask = 2147483647; 243 | files = ( 244 | ); 245 | runOnlyForDeploymentPostprocessing = 0; 246 | }; 247 | /* End PBXResourcesBuildPhase section */ 248 | 249 | /* Begin PBXShellScriptBuildPhase section */ 250 | 02E82B16C38838BFAAE5AD04 /* [CP] Check Pods Manifest.lock */ = { 251 | isa = PBXShellScriptBuildPhase; 252 | buildActionMask = 2147483647; 253 | files = ( 254 | ); 255 | inputFileListPaths = ( 256 | ); 257 | inputPaths = ( 258 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 259 | "${PODS_ROOT}/Manifest.lock", 260 | ); 261 | name = "[CP] Check Pods Manifest.lock"; 262 | outputFileListPaths = ( 263 | ); 264 | outputPaths = ( 265 | "$(DERIVED_FILE_DIR)/Pods-Example-checkManifestLockResult.txt", 266 | ); 267 | runOnlyForDeploymentPostprocessing = 0; 268 | shellPath = /bin/sh; 269 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 270 | showEnvVarsInLog = 0; 271 | }; 272 | B29FFF59C84F8D2B1B7169E3 /* [CP] Embed Pods Frameworks */ = { 273 | isa = PBXShellScriptBuildPhase; 274 | buildActionMask = 2147483647; 275 | files = ( 276 | ); 277 | inputPaths = ( 278 | "${PODS_ROOT}/Target Support Files/Pods-swiftVibrant_Tests/Pods-swiftVibrant_Tests-frameworks.sh", 279 | "${BUILT_PRODUCTS_DIR}/swiftVibrant-iOS9.3/swiftVibrant.framework", 280 | ); 281 | name = "[CP] Embed Pods Frameworks"; 282 | outputPaths = ( 283 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/swiftVibrant.framework", 284 | ); 285 | runOnlyForDeploymentPostprocessing = 0; 286 | shellPath = /bin/sh; 287 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-swiftVibrant_Tests/Pods-swiftVibrant_Tests-frameworks.sh\"\n"; 288 | showEnvVarsInLog = 0; 289 | }; 290 | C1CB5EA0B6D4E1478AF9E75C /* [CP] Check Pods Manifest.lock */ = { 291 | isa = PBXShellScriptBuildPhase; 292 | buildActionMask = 2147483647; 293 | files = ( 294 | ); 295 | inputFileListPaths = ( 296 | ); 297 | inputPaths = ( 298 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 299 | "${PODS_ROOT}/Manifest.lock", 300 | ); 301 | name = "[CP] Check Pods Manifest.lock"; 302 | outputFileListPaths = ( 303 | ); 304 | outputPaths = ( 305 | "$(DERIVED_FILE_DIR)/Pods-swiftVibrant_Tests-checkManifestLockResult.txt", 306 | ); 307 | runOnlyForDeploymentPostprocessing = 0; 308 | shellPath = /bin/sh; 309 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 310 | showEnvVarsInLog = 0; 311 | }; 312 | E35689F2C1DB016A71102447 /* [CP] Embed Pods Frameworks */ = { 313 | isa = PBXShellScriptBuildPhase; 314 | buildActionMask = 2147483647; 315 | files = ( 316 | ); 317 | inputPaths = ( 318 | "${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks.sh", 319 | "${BUILT_PRODUCTS_DIR}/swiftVibrant-iOS13.4/swiftVibrant.framework", 320 | ); 321 | name = "[CP] Embed Pods Frameworks"; 322 | outputPaths = ( 323 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/swiftVibrant.framework", 324 | ); 325 | runOnlyForDeploymentPostprocessing = 0; 326 | shellPath = /bin/sh; 327 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks.sh\"\n"; 328 | showEnvVarsInLog = 0; 329 | }; 330 | /* End PBXShellScriptBuildPhase section */ 331 | 332 | /* Begin PBXSourcesBuildPhase section */ 333 | 02CE3D52245EF19700E786F8 /* Sources */ = { 334 | isa = PBXSourcesBuildPhase; 335 | buildActionMask = 2147483647; 336 | files = ( 337 | 02CE3D5D245EF19700E786F8 /* ViewController.swift in Sources */, 338 | 02CE3D59245EF19700E786F8 /* AppDelegate.swift in Sources */, 339 | 02CE3D5B245EF19700E786F8 /* SceneDelegate.swift in Sources */, 340 | ); 341 | runOnlyForDeploymentPostprocessing = 0; 342 | }; 343 | 607FACE11AFB9204008FA782 /* Sources */ = { 344 | isa = PBXSourcesBuildPhase; 345 | buildActionMask = 2147483647; 346 | files = ( 347 | 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */, 348 | ); 349 | runOnlyForDeploymentPostprocessing = 0; 350 | }; 351 | /* End PBXSourcesBuildPhase section */ 352 | 353 | /* Begin PBXVariantGroup section */ 354 | 02CE3D5E245EF19700E786F8 /* Main.storyboard */ = { 355 | isa = PBXVariantGroup; 356 | children = ( 357 | 02CE3D5F245EF19700E786F8 /* Base */, 358 | ); 359 | name = Main.storyboard; 360 | sourceTree = ""; 361 | }; 362 | 02CE3D63245EF19900E786F8 /* LaunchScreen.storyboard */ = { 363 | isa = PBXVariantGroup; 364 | children = ( 365 | 02CE3D64245EF19900E786F8 /* Base */, 366 | ); 367 | name = LaunchScreen.storyboard; 368 | sourceTree = ""; 369 | }; 370 | /* End PBXVariantGroup section */ 371 | 372 | /* Begin XCBuildConfiguration section */ 373 | 02CE3D68245EF19900E786F8 /* Debug */ = { 374 | isa = XCBuildConfiguration; 375 | baseConfigurationReference = 04DDE9579463834E92864267 /* Pods-Example.debug.xcconfig */; 376 | buildSettings = { 377 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 378 | CLANG_ANALYZER_NONNULL = YES; 379 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 380 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 381 | CLANG_ENABLE_OBJC_WEAK = YES; 382 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 383 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 384 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 385 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 386 | CODE_SIGN_STYLE = Automatic; 387 | DEBUG_INFORMATION_FORMAT = dwarf; 388 | DEVELOPMENT_TEAM = GDNLAH489Z; 389 | GCC_C_LANGUAGE_STANDARD = gnu11; 390 | INFOPLIST_FILE = Example/Info.plist; 391 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 392 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 393 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 394 | MTL_FAST_MATH = YES; 395 | PRODUCT_BUNDLE_IDENTIFIER = com.bd452.SwiftVibrantExample; 396 | PRODUCT_NAME = "$(TARGET_NAME)"; 397 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 398 | SWIFT_VERSION = 5.0; 399 | TARGETED_DEVICE_FAMILY = "1,2"; 400 | }; 401 | name = Debug; 402 | }; 403 | 02CE3D69245EF19900E786F8 /* Release */ = { 404 | isa = XCBuildConfiguration; 405 | baseConfigurationReference = 631AEB3044C059FA9826ECDF /* Pods-Example.release.xcconfig */; 406 | buildSettings = { 407 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 408 | CLANG_ANALYZER_NONNULL = YES; 409 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 410 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 411 | CLANG_ENABLE_OBJC_WEAK = YES; 412 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 413 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 414 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 415 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 416 | CODE_SIGN_STYLE = Automatic; 417 | DEVELOPMENT_TEAM = GDNLAH489Z; 418 | GCC_C_LANGUAGE_STANDARD = gnu11; 419 | INFOPLIST_FILE = Example/Info.plist; 420 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 421 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 422 | MTL_FAST_MATH = YES; 423 | PRODUCT_BUNDLE_IDENTIFIER = com.bd452.SwiftVibrantExample; 424 | PRODUCT_NAME = "$(TARGET_NAME)"; 425 | SWIFT_VERSION = 5.0; 426 | TARGETED_DEVICE_FAMILY = "1,2"; 427 | }; 428 | name = Release; 429 | }; 430 | 607FACED1AFB9204008FA782 /* Debug */ = { 431 | isa = XCBuildConfiguration; 432 | buildSettings = { 433 | ALWAYS_SEARCH_USER_PATHS = NO; 434 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 435 | CLANG_CXX_LIBRARY = "libc++"; 436 | CLANG_ENABLE_MODULES = YES; 437 | CLANG_ENABLE_OBJC_ARC = YES; 438 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 439 | CLANG_WARN_BOOL_CONVERSION = YES; 440 | CLANG_WARN_COMMA = YES; 441 | CLANG_WARN_CONSTANT_CONVERSION = YES; 442 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 443 | CLANG_WARN_EMPTY_BODY = YES; 444 | CLANG_WARN_ENUM_CONVERSION = YES; 445 | CLANG_WARN_INFINITE_RECURSION = YES; 446 | CLANG_WARN_INT_CONVERSION = YES; 447 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 448 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 449 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 450 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 451 | CLANG_WARN_STRICT_PROTOTYPES = YES; 452 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 453 | CLANG_WARN_UNREACHABLE_CODE = YES; 454 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 455 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 456 | COPY_PHASE_STRIP = NO; 457 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 458 | ENABLE_STRICT_OBJC_MSGSEND = YES; 459 | ENABLE_TESTABILITY = YES; 460 | GCC_C_LANGUAGE_STANDARD = gnu99; 461 | GCC_DYNAMIC_NO_PIC = NO; 462 | GCC_NO_COMMON_BLOCKS = YES; 463 | GCC_OPTIMIZATION_LEVEL = 0; 464 | GCC_PREPROCESSOR_DEFINITIONS = ( 465 | "DEBUG=1", 466 | "$(inherited)", 467 | ); 468 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 469 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 470 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 471 | GCC_WARN_UNDECLARED_SELECTOR = YES; 472 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 473 | GCC_WARN_UNUSED_FUNCTION = YES; 474 | GCC_WARN_UNUSED_VARIABLE = YES; 475 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 476 | MTL_ENABLE_DEBUG_INFO = YES; 477 | ONLY_ACTIVE_ARCH = YES; 478 | SDKROOT = iphoneos; 479 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 480 | }; 481 | name = Debug; 482 | }; 483 | 607FACEE1AFB9204008FA782 /* Release */ = { 484 | isa = XCBuildConfiguration; 485 | buildSettings = { 486 | ALWAYS_SEARCH_USER_PATHS = NO; 487 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 488 | CLANG_CXX_LIBRARY = "libc++"; 489 | CLANG_ENABLE_MODULES = YES; 490 | CLANG_ENABLE_OBJC_ARC = YES; 491 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 492 | CLANG_WARN_BOOL_CONVERSION = YES; 493 | CLANG_WARN_COMMA = YES; 494 | CLANG_WARN_CONSTANT_CONVERSION = YES; 495 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 496 | CLANG_WARN_EMPTY_BODY = YES; 497 | CLANG_WARN_ENUM_CONVERSION = YES; 498 | CLANG_WARN_INFINITE_RECURSION = YES; 499 | CLANG_WARN_INT_CONVERSION = YES; 500 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 501 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 502 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 503 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 504 | CLANG_WARN_STRICT_PROTOTYPES = YES; 505 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 506 | CLANG_WARN_UNREACHABLE_CODE = YES; 507 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 508 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 509 | COPY_PHASE_STRIP = NO; 510 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 511 | ENABLE_NS_ASSERTIONS = NO; 512 | ENABLE_STRICT_OBJC_MSGSEND = YES; 513 | GCC_C_LANGUAGE_STANDARD = gnu99; 514 | GCC_NO_COMMON_BLOCKS = YES; 515 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 516 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 517 | GCC_WARN_UNDECLARED_SELECTOR = YES; 518 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 519 | GCC_WARN_UNUSED_FUNCTION = YES; 520 | GCC_WARN_UNUSED_VARIABLE = YES; 521 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 522 | MTL_ENABLE_DEBUG_INFO = NO; 523 | SDKROOT = iphoneos; 524 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 525 | VALIDATE_PRODUCT = YES; 526 | }; 527 | name = Release; 528 | }; 529 | 607FACF31AFB9204008FA782 /* Debug */ = { 530 | isa = XCBuildConfiguration; 531 | baseConfigurationReference = 1BF125B0EC659770D6757116 /* Pods-swiftVibrant_Tests.debug.xcconfig */; 532 | buildSettings = { 533 | DEVELOPMENT_TEAM = GDNLAH489Z; 534 | FRAMEWORK_SEARCH_PATHS = ( 535 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 536 | "$(inherited)", 537 | ); 538 | GCC_PREPROCESSOR_DEFINITIONS = ( 539 | "DEBUG=1", 540 | "$(inherited)", 541 | ); 542 | INFOPLIST_FILE = Tests/Info.plist; 543 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 544 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 545 | PRODUCT_NAME = "$(TARGET_NAME)"; 546 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 547 | SWIFT_VERSION = 4.0; 548 | }; 549 | name = Debug; 550 | }; 551 | 607FACF41AFB9204008FA782 /* Release */ = { 552 | isa = XCBuildConfiguration; 553 | baseConfigurationReference = 04B2C6316ED3FEA12D2E413E /* Pods-swiftVibrant_Tests.release.xcconfig */; 554 | buildSettings = { 555 | DEVELOPMENT_TEAM = GDNLAH489Z; 556 | FRAMEWORK_SEARCH_PATHS = ( 557 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 558 | "$(inherited)", 559 | ); 560 | INFOPLIST_FILE = Tests/Info.plist; 561 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 562 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 563 | PRODUCT_NAME = "$(TARGET_NAME)"; 564 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 565 | SWIFT_VERSION = 4.0; 566 | }; 567 | name = Release; 568 | }; 569 | /* End XCBuildConfiguration section */ 570 | 571 | /* Begin XCConfigurationList section */ 572 | 02CE3D67245EF19900E786F8 /* Build configuration list for PBXNativeTarget "Example" */ = { 573 | isa = XCConfigurationList; 574 | buildConfigurations = ( 575 | 02CE3D68245EF19900E786F8 /* Debug */, 576 | 02CE3D69245EF19900E786F8 /* Release */, 577 | ); 578 | defaultConfigurationIsVisible = 0; 579 | defaultConfigurationName = Release; 580 | }; 581 | 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "swiftVibrant" */ = { 582 | isa = XCConfigurationList; 583 | buildConfigurations = ( 584 | 607FACED1AFB9204008FA782 /* Debug */, 585 | 607FACEE1AFB9204008FA782 /* Release */, 586 | ); 587 | defaultConfigurationIsVisible = 0; 588 | defaultConfigurationName = Release; 589 | }; 590 | 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "swiftVibrant_Tests" */ = { 591 | isa = XCConfigurationList; 592 | buildConfigurations = ( 593 | 607FACF31AFB9204008FA782 /* Debug */, 594 | 607FACF41AFB9204008FA782 /* Release */, 595 | ); 596 | defaultConfigurationIsVisible = 0; 597 | defaultConfigurationName = Release; 598 | }; 599 | /* End XCConfigurationList section */ 600 | }; 601 | rootObject = 607FACC81AFB9204008FA782 /* Project object */; 602 | } 603 | -------------------------------------------------------------------------------- /Example/swiftVibrant.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/swiftVibrant.xcodeproj/xcshareddata/xcschemes/swiftVibrant-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 80 | 82 | 88 | 89 | 90 | 91 | 92 | 93 | 99 | 101 | 107 | 108 | 109 | 110 | 112 | 113 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Bryce Dougherty 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swift-vibrant 2 | 3 | Generate a color palette from a UIImage. 4 | 5 | Other color palette generators will only give you a list of the dominant colors in an image. Vibrant does much more. 6 | 7 | Now I know what you're thinking: "But Bryce, colors from an image! That's what I want! That's why I'm on your repository in the first place!" 8 | 9 | Well I've got news for you: swift-vibrant (or [node-vibrant](https://github.com/Vibrant-Colors/node-vibrant/), if you use JavaScript. No affiliation but that's what this project is based off of) will not only give you the dominant colors in an image, oh no. It will *also* give you a fully featured *palette*. What's the difference you might ask? 10 | 11 | It provides: 12 | * A Vibrant Color (Duh) 13 | * A Muted Color 14 | * A Dark Vibrant Color 15 | * A Dark Muted Color 16 | * A Light Vibrant Color 17 | * A Light Muted Color 18 | 19 | As well as 2 different text colors for each of the above that look good on the color they're designed for. 20 | 21 | Don't believe me? There's a 100% money back guarantee! (Just kidding, it's MIT licensed aka free for your use). 22 | 23 | ## Installation 24 | 25 | swift-vibrant is available through [CocoaPods](https://cocoapods.org). To install 26 | it, simply add the following line to your Podfile: 27 | 28 | ```ruby 29 | pod 'swift-vibrant' 30 | ``` 31 | 32 | ## Usage 33 | ```swift 34 | import swiftVibrant 35 | 36 | let image = UIImage(named: "name_of_image") 37 | 38 | // Calling from a background thread 39 | Vibrant.from(image).getPalette({ palette in 40 | // do stuff with your palette 41 | }) 42 | // Calling in main thread 43 | Vibrant.from(image).getPalette() 44 | /// do stuff with your palette here 45 | 46 | // Using constructor 47 | let v = Vibrant(image, Vibrant.Options) 48 | v.getPalette({ palette in 49 | // do stuff with your palette 50 | }) 51 | ``` 52 | 53 | 54 | ## References 55 | 56 | ### `Vibrant` 57 | Main class of `swift-vibrant`. 58 | 59 | #### `Vibrant.from(src: UIImage): Builder` 60 | Make a `Builder` for an image. Returns a `Builder` instance. 61 | 62 | #### `constructor(src: UIImage, opts: Vibrant.Options)` 63 | 64 | Name | Description 65 | ------- | --------------------------------------- 66 | `image` | Your UIImage instance 67 | `opts` | Options (optional) 68 | 69 | 70 | ##### `Options` 71 | 72 | ```swift 73 | public struct Options { 74 | var colorCount: Int = 64 75 | var quality: Int = 5 76 | var quantizer: Quantizer.quantizer 77 | var generator: Generator.generator 78 | var maxDimension: CGFloat? 79 | var filters: [Filter] 80 | } 81 | ``` 82 | 83 | Field | Default | Description 84 | -------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- 85 | `colorCount` | 64 | amount of colors in initial palette from which the swatches will be generated 86 | `quality` | 5 | Scale down factor used in downsampling stage. `1` means no downsampling. If `maxDimension` is set, this value will not be used. 87 | `quantizer` | `Quantizer.defaultQuantizer` | A `Quantizer` function 88 | `generator` | `Generator.defaultGenerator` | An `Generator` function 89 | `maxDimension` | `nil` | The max size of the image's longer side used in downsampling stage. This field will override `quality`. 90 | `filters` | `[]` | An array of `Filter` 91 | 92 | ##### `quantizer` 93 | 94 | ```swift 95 | public typealias quantizer = (_ pixels: [UInt8], _ options: Vibrant.Options)->[Swatch] 96 | ``` 97 | 98 | ##### `generator` 99 | 100 | ```swift 101 | public typealias generator = (_ swatches: [Swatch])->Palette 102 | ``` 103 | 104 | 105 | ##### `Filter` 106 | 107 | Returns `true` if the color is to be kept. 108 | 109 | ```swift 110 | public class Filter { 111 | public typealias filterFunction = (_ red: UInt8, _ green: UInt8, _ blue: UInt8, _ alpha: UInt8)->Bool 112 | 113 | var f: filterFunction 114 | } 115 | ``` 116 | 117 | #### `getPalette(cb: (Palette)->Void)` 118 | 119 | Name | Description 120 | ---- | ----------------- 121 | `cb` | callback function. 122 | 123 | ### `Vibrant.Builder` 124 | Helper class for change configurations and create a `Vibrant` instance. Methods of a `Builder` instance can be chained like: 125 | 126 | ```ts 127 | Vibrant.from(src) 128 | .quality(1) 129 | .clearFilters() 130 | // ... 131 | .getPalette() 132 | ``` 133 | 134 | #### `quality(q: Int): Builder` 135 | Sets `opts.quality` to `q`. Returns this `Builder` instance. 136 | 137 | #### `maxColorCount(n: Int): Builder` 138 | Sets `opts.colorCount` to `n`. Returns this `Builder` instance. 139 | 140 | #### `maxDimension(d: Int): Builder` 141 | Sets `opts.maxDimension` to `d`. Returns this `Builder` instance. 142 | 143 | #### `addFilter(f: Filter): Builder` 144 | Adds a filter function. Returns this `Builder` instance. 145 | 146 | #### `removeFilter(f: Filter): Builder` 147 | Removes a filter function. Returns this `Builder` instance. 148 | 149 | #### `clearFilters(): Builder` 150 | Clear all filters. Returns this `Builder` instance. 151 | 152 | #### `useQuantizer(quantizer: Quantizer.quantizer): Builder` 153 | Specifies which `Quantizer` implementation class to use. Returns this `Builder` instance. 154 | 155 | #### `useGenerator(generator: Generator.generator): Builder` 156 | Sets `opts.generator` to `generator`. Returns this `Builder` instance. 157 | 158 | #### `build(): Vibrant` 159 | Builds and returns a `Vibrant` instance as configured. 160 | 161 | #### `getPalette(cb: (Palette)->Void)` 162 | Builds a `Vibrant` instance as configured and calls its `getPalette` method on a background thread. Calls `cb` on main thread. 163 | 164 | #### `getPalette()->Palette` 165 | 166 | Builds a `Vibrant` instance as configured and calls its `getPalette` method on the main thread. 167 | 168 | ### `Vibrant.Swatch` 169 | Represents a color swatch generated from an image's palette. 170 | 171 | #### `Vec3` 172 | 173 | ```swift 174 | public typealias Vec3 = (T, T, T) 175 | ``` 176 | 177 | #### `RGB` 178 | 179 | ```swift 180 | public typealias RGB = (r: UInt8, g: UInt8, b: UInt8) 181 | ``` 182 | 183 | #### `HSL` 184 | 185 | ```swift 186 | public typealias HSL = (r: Double, g: Double, b: Double) 187 | ``` 188 | 189 | #### `XYZ` 190 | 191 | ```swift 192 | public typealias HSL = (r: Double, g: Double, b: Double) 193 | ``` 194 | 195 | #### `LAB` 196 | 197 | ```swift 198 | public typealias HSL = (r: Double, g: Double, b: Double) 199 | ``` 200 | 201 | #### 202 | 203 | #### `constructor(rgb: RGB, population: Int)` 204 | 205 | Internal use. 206 | 207 | Name | Description 208 | ------------ | ----------------------------------- 209 | `rgb` | `[r, g, b]` 210 | `population` | Population of the color in an image 211 | 212 | #### `hsl: HSL` 213 | #### `getPopulation(): Int` 214 | #### `rgb: RGB` 215 | #### `hex: String` 216 | 217 | Returns a hexadecimal value of the swatch 218 | 219 | #### `getTitleTextColor(): UIColor` 220 | Returns an appropriate color to use for any 'title' text which is displayed over this `Swatch`'s color. 221 | 222 | #### `getBodyTextColor(): UIColor` 223 | Returns an appropriate color to use for any 'body' text which is displayed over this `Swatch`'s color. 224 | 225 | ### `Vibrant.Util` 226 | Utility methods. Internal usage. 227 | 228 | #### `hexToRgb(hex: string): RGB` 229 | #### `rgbToHex(r: UInt8, g: UInt8, b: UInt8): String` 230 | #### `hslToRgb(h: Double, s: Double, l: Double): RGB` 231 | #### `rgbToHsl(r: UInt8, g: UInt8, b: UInt8): HSL` 232 | #### `xyzToRgb(x: Double, y: Double, z: Double): RGB` 233 | #### `rgbToXyz(r: UInt8, g: UInt8, b: UInt8): XYZ` 234 | #### `xyzToCIELab(x: Double, y: Double, z: Double): LAB` 235 | #### `rgbToCIELab(l: UInt8, a: UInt8, b: UInt8): LAB` 236 | #### `deltaE94(lab1: Double, lab2: Double): Double` 237 | Computes CIE delta E 1994 diff between `lab1` and `lab2`. The 2 colors are in CIE-Lab color space. Used in tests to compare 2 colors' perceptual similarity. 238 | 239 | #### `rgbDiff(rgb1: RGB, rgb2: RGB): Double` 240 | Compute CIE delta E 1994 diff between `rgb1` and `rgb2`. 241 | 242 | #### `hexDiff(hex1: String, hex2: String): Double` 243 | Compute CIE delta E 1994 diff between `hex1` and `hex2`. 244 | 245 | #### `getColorDiffStatus(d: Double): String` 246 | Gets a string to describe the meaning of the color diff. Used in tests. 247 | 248 | Delta E | Perception | Returns 249 | -------- | -------------------------------------- | ----------- 250 | <= 1.0 | Not perceptible by human eyes. | `"Perfect"` 251 | 1 - 2 | Perceptible through close observation. | `"Close"` 252 | 2 - 10 | Perceptible at a glance. | `"Good"` 253 | 11 - 49 | Colors are more similar than opposite | `"Similar"` 254 | 50 - 100 | Colors are exact opposite | `Wrong` 255 | 256 | ## Notes 257 | - This library uses code from [ColorThiefSwift](https://github.com/yamoridon/ColorThiefSwift), mostly borrowing from their implementation of the modified median cut quantization (MMCQ) algorithm 258 | - A majority of the rest was adapted from [node-vibrant](https://github.com/Vibrant-Colors/node-vibrant/) 259 | 260 | ## Author 261 | 262 | Bryce Dougherty: bryce.dougherty@gmail.com 263 | 264 | ## Credits 265 | 266 | Kazuki Ohara: [ColorThiefSwift](https://github.com/yamoridon/ColorThiefSwift) 267 | 268 | Jari Zwarts: [node-vibrant](https://github.com/Vibrant-Colors/node-vibrant/) 269 | 270 | ## License 271 | 272 | swiftVibrant is available under the MIT license. See the LICENSE file for more info. 273 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj -------------------------------------------------------------------------------- /swift-vibrant.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint swiftVibrant.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'swift-vibrant' 11 | s.version = '0.1.0' 12 | s.summary = 'Extract prominent colors from an image.' 13 | 14 | # This description is used to generate tags and improve search results. 15 | # * Think: What does it do? Why did you write it? What is the focus? 16 | # * Try to keep it short, snappy and to the point. 17 | # * Write the description between the DESC delimiters below. 18 | # * Finally, don't worry about the indent, CocoaPods strips it! 19 | 20 | s.description = <<-DESC 21 | A swift port of the node-vibrant npm module. Extracts the dominant colors from an image and returns them as an easily digestible palette. 22 | DESC 23 | 24 | s.homepage = 'https://github.com/bd452/swift-vibrant' 25 | s.license = { :type => 'MIT', :file => 'LICENSE' } 26 | s.author = { 'Bryce Dougherty' => 'bryce.dougherty@gmail.com' } 27 | s.source = { :git => 'https://github.com/bd452/swift-vibrant.git', :tag => s.version.to_s } 28 | 29 | s.ios.deployment_target = '8.0' 30 | 31 | s.source_files = 'swiftVibrant/**/*' 32 | 33 | s.swift_version = '5.0' 34 | 35 | # s.resource_bundles = { 36 | # 'swiftVibrant' => ['swiftVibrant/Assets/*.png'] 37 | # } 38 | 39 | # s.public_header_files = 'Pod/Classes/**/*.h' 40 | # s.frameworks = 'UIKit', 'MapKit' 41 | # s.dependency 'AFNetworking', '~> 2.3' 42 | end 43 | -------------------------------------------------------------------------------- /swiftVibrant/Builder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Builder.swift 3 | // swift-vibrant-ios 4 | // 5 | // Created by Bryce Dougherty on 5/3/20. 6 | // Copyright © 2020 Bryce Dougherty. All rights reserved. 7 | // 8 | 9 | 10 | import UIKit 11 | 12 | public typealias Callback = (T)->Void 13 | 14 | public class Builder { 15 | 16 | private var _src: UIImage 17 | private var _opts: Vibrant.Options 18 | 19 | init(_ src: UIImage, _ opts: Vibrant.Options = Vibrant.Options()) { 20 | self._src = src 21 | self._opts = opts 22 | } 23 | public func maxColorCount(_ n: Int)->Builder { 24 | self._opts.colorCount = n 25 | return self 26 | } 27 | 28 | public func maxDimension(_ d: CGFloat)->Builder { 29 | self._opts.maxDimension = d 30 | return self 31 | } 32 | 33 | public func quality(_ q: Int)->Builder { 34 | self._opts.quality = q 35 | return self 36 | } 37 | 38 | public func addFilter(_ f: Filter)->Builder { 39 | self._opts.filters.append(f) 40 | return self 41 | } 42 | 43 | public func removeFilter(_ f: Filter)->Builder { 44 | self._opts.filters.removeAll(where: { (callback: Filter) in 45 | callback.id == f.id 46 | }) 47 | return self 48 | } 49 | 50 | public func useGenerator(_ generator: @escaping Generator.generator)->Builder { 51 | self._opts.generator = generator 52 | return self 53 | } 54 | 55 | public func useQuantizer(_ quantizer: @escaping Quantizer.quantizer)->Builder { 56 | self._opts.quantizer = quantizer 57 | return self 58 | } 59 | 60 | public func build()->Vibrant { 61 | return Vibrant(src: self._src, opts: self._opts) 62 | } 63 | public func getPalette()->Palette { 64 | return self.build().getPalette() 65 | } 66 | public func getPalette(_ cb: @escaping Callback) { 67 | return self.build().getPalette(cb) 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /swiftVibrant/ColorConverters.swift: -------------------------------------------------------------------------------- 1 | // 2 | // util.swift 3 | // swift-vibrant 4 | // 5 | // Created by Bryce Dougherty on 4/30/20. 6 | // Copyright © 2020 Bryce Dougherty. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | 13 | struct DELTAE94_DIFF_STATUS { 14 | static let NA:Int = 0 15 | static let PERFECT:Int = 1 16 | static let CLOSE:Int = 2 17 | static let GOOD:Int = 10 18 | static let SIMILAR:Int = 50 19 | } 20 | 21 | //public typealias Double = Double 22 | 23 | struct newErr: Error { 24 | init(_ message: String) { 25 | self.message = message 26 | } 27 | let message: String 28 | } 29 | 30 | public func uiColorToRgb(_ color: UIColor)->RGB { 31 | var r: CGFloat = 0 32 | var g: CGFloat = 0 33 | var b: CGFloat = 0 34 | color.getRed(&r, green: &g, blue: &b, alpha: nil) 35 | return (UInt8(r * 255), UInt8(g * 255), UInt8(b * 255)) 36 | } 37 | 38 | public func rgbToUIColor(_ r: UInt8, _ g: UInt8, _ b: UInt8)->UIColor { 39 | return UIColor.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: 1) 40 | } 41 | public func uiColorToHsl(_ color: UIColor)->HSL { 42 | var h:CGFloat = 0 43 | var s:CGFloat = 0 44 | var l:CGFloat = 0 45 | color.getHue(&h, saturation: &s, brightness: &l, alpha: nil) 46 | return (Double(h),Double(s),Double(l)) 47 | } 48 | public func hslToUIColor(_ h: Double, _ s: Double, _ l: Double)->UIColor { 49 | return UIColor.init(hue: CGFloat(h), saturation: CGFloat(s), brightness: CGFloat(l), alpha: 1) 50 | } 51 | 52 | 53 | public func hexToRgb(_ hex: String)->RGB? { 54 | let r, g, b: UInt8 55 | 56 | if hex.hasPrefix("#") { 57 | let start = hex.index(hex.startIndex, offsetBy: 1) 58 | let hexColor = String(hex[start...]) 59 | 60 | if hexColor.count == 8 { 61 | let scanner = Scanner(string: hexColor) 62 | var hexDouble: UInt64 = 0 63 | 64 | 65 | if scanner.scanHexInt64(&hexDouble) { 66 | r = UInt8(hexDouble & 0xff000000) >> 24 67 | g = UInt8(hexDouble & 0x00ff0000) >> 16 68 | b = UInt8(hexDouble & 0x0000ff00) >> 8 69 | 70 | return (r, g, b) 71 | } 72 | } 73 | } 74 | return nil 75 | } 76 | 77 | 78 | 79 | public func rgbToHex(_ r: UInt8, _ g: UInt8, _ b: UInt8)->String { 80 | return "#" + String(format:"%02X", r) + String(format:"%02X", g) + String(format:"%02X", b) 81 | } 82 | 83 | public func rgbToHsl(r: UInt8, g: UInt8, b: UInt8)-> Vec3 { 84 | let r = Double(r) / 255 85 | let g = Double(g) / 255 86 | let b = Double(b) / 255 87 | let maxVal = max(r, g, b) 88 | let minVal = min(r, g, b) 89 | var h: Double 90 | let s: Double 91 | let l = (maxVal + minVal) / 2 92 | if (maxVal == minVal) { 93 | h = 0 94 | s = 0 95 | } else { 96 | let d = maxVal - minVal 97 | s = l > 0.5 ? d / (2 - maxVal - minVal) : d / (maxVal + minVal) 98 | switch (maxVal) { 99 | case r: 100 | h = (g - b) / d + (g < b ? 6 : 0) 101 | break 102 | case g: 103 | h = (b - r) / d + 2 104 | break 105 | case b: 106 | h = (r - g) / d + 4 107 | break 108 | default: 109 | h = 0 110 | break 111 | } 112 | h /= 6 113 | } 114 | return (h, s, l) 115 | } 116 | 117 | public func hslToRgb(_ h: Double, _ s: Double, _ l: Double)-> RGB { 118 | var r: Double 119 | var g: Double 120 | var b: Double 121 | 122 | func hue2rgb(_ p: Double, _ q: Double, _ t: Double)-> Double { 123 | var t = t 124 | if (t < 0) { t += 1 } 125 | if (t > 1) { t -= 1 } 126 | if (t < 1 / 6) { return p + (q - p) * 6 * t } 127 | if (t < 1 / 2) { return q } 128 | if (t < 2 / 3) { return p + (q - p) * (2 / 3 - t) * 6 } 129 | return p 130 | } 131 | 132 | if (s == 0) { 133 | r = l 134 | g = l 135 | b = l 136 | } else { 137 | let q = l < 0.5 ? l * (1 + s) : l + s - (l * s) 138 | let p = 2 * l - q 139 | r = hue2rgb(p, q, h + 1 / 3) 140 | g = hue2rgb(p, q, h) 141 | b = hue2rgb(p, q, h - (1 / 3)) 142 | } 143 | return( 144 | UInt8(r * 255), 145 | UInt8(g * 255), 146 | UInt8(b * 255) 147 | ) 148 | } 149 | 150 | 151 | public func rgbToXyz(_ r: UInt8, _ g: UInt8, _ b: UInt8)->XYZ { 152 | var r = Double(r) / 255 153 | var g = Double(g) / 255 154 | var b = Double(b) / 255 155 | 156 | r = r > 0.04045 ? pow((r + 0.005) / 1.055, 2.4) : r / 12.92 157 | g = g > 0.04045 ? pow((g + 0.005) / 1.055, 2.4) : g / 12.92 158 | b = b > 0.04045 ? pow((b + 0.005) / 1.055, 2.4) : b / 12.92 159 | 160 | r *= 100 161 | g *= 100 162 | b *= 100 163 | 164 | let x = r * 0.4124 + g * 0.3576 + b * 0.1805 165 | let y = r * 0.2126 + g * 0.7152 + b * 0.0722 166 | let z = r * 0.0193 + g * 0.1192 + b * 0.9505 167 | 168 | return (x: x,y: y,z: z) 169 | } 170 | 171 | public func xyzToCIELab(_ x: Double, _ y: Double, _ z: Double)-> LAB { 172 | let REF_X: Double = 95.047 173 | let REF_Y: Double = 100 174 | let REF_Z: Double = 108.883 175 | 176 | var x = x / REF_X 177 | var y = y / REF_Y 178 | var z = z / REF_Z 179 | 180 | x = x > 0.008856 ? pow(x, 1 / 3) : 7.787 * x + 16 / 116 181 | y = y > 0.008856 ? pow(y, 1 / 3) : 7.787 * y + 16 / 116 182 | z = z > 0.008856 ? pow(z, 1 / 3) : 7.787 * z + 16 / 116 183 | 184 | let L = 116 * y - 16 185 | let a = 500 * (x - y) 186 | let b = 200 * (y - z) 187 | 188 | return (L: L, a: a, b: b) 189 | } 190 | 191 | 192 | public func rgbToCIELab(_ r: UInt8, _ g: UInt8, _ b: UInt8)->LAB { 193 | let (x,y,z) = rgbToXyz(r, g, b) 194 | return xyzToCIELab(x, y, z) 195 | } 196 | 197 | public func deltaE94(_ lab1: Vec3, _ lab2: Vec3)->Double { 198 | let WEIGHT_L:Double = 1 199 | let WEIGHT_C:Double = 1 200 | let WEIGHT_H:Double = 1 201 | 202 | let (L1, a1, b1) = lab1 203 | let (L2, a2, b2) = lab2 204 | let dL = L1 - L2 205 | let da = a1 - a2 206 | let db = b1 - b2 207 | 208 | let xC1 = sqrt(a1 * a1 + b1 * b1) 209 | let xC2 = sqrt(a2 * a2 + b2 * b2) 210 | 211 | var xDL = L2 - L1 212 | var xDC = xC2 - xC1 213 | let xDE = sqrt(dL * dL + da * da + db * db) 214 | 215 | var xDH = (sqrt(xDE) > sqrt(abs(xDL)) + sqrt(abs(xDC))) 216 | ? sqrt(xDE * xDE - xDL * xDL - xDC * xDC) 217 | : 0 218 | 219 | let xSC = 1 + 0.045 * xC1 220 | let xSH = 1 + 0.015 * xC1 221 | 222 | xDL /= WEIGHT_L 223 | xDC /= WEIGHT_C * xSC 224 | xDH /= WEIGHT_H * xSH 225 | 226 | return sqrt(xDL * xDL + xDC * xDC + xDH * xDH) 227 | } 228 | 229 | public func rgbDiff(_ rgb1: RGB, _ rgb2: RGB)->Double { 230 | let lab1 = apply(rgbToCIELab, rgb1) 231 | let lab2 = apply(rgbToCIELab, rgb2) 232 | return deltaE94(lab1, lab2) 233 | } 234 | 235 | public func hexDiff(_ hex1: String, _ hex2: String)->Double { 236 | let rgb1 = hexToRgb(hex1)! 237 | let rgb2 = hexToRgb(hex2)! 238 | return rgbDiff(rgb1, rgb2) 239 | } 240 | 241 | public func getColorDiffStatus(_ d: Int)->String { 242 | if (d < DELTAE94_DIFF_STATUS.NA) { return "N/A" } 243 | // Not perceptible by human eyes 244 | if (d <= DELTAE94_DIFF_STATUS.PERFECT) { return "Perfect" } 245 | // Perceptible through close observation 246 | if (d <= DELTAE94_DIFF_STATUS.CLOSE) { return "Close" } 247 | // Perceptible at a glance 248 | if (d <= DELTAE94_DIFF_STATUS.GOOD) { return "Good" } 249 | // Colors are more similar than opposite 250 | if (d < DELTAE94_DIFF_STATUS.SIMILAR) { return "Similar" } 251 | return "Wrong" 252 | } 253 | 254 | -------------------------------------------------------------------------------- /swiftVibrant/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // swift-vibrant 4 | // 5 | // Created by Bryce Dougherty on 5/3/20. 6 | // Copyright © 2020 Bryce Dougherty. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | internal let signalBits = 5 12 | internal let rightShift = 8 - signalBits 13 | internal let multiplier = 1 << rightShift 14 | internal let histogramSize = 1 << (3 * signalBits) 15 | internal let vboxLength = 1 << signalBits 16 | internal let fractionByPopulation = 0.75 17 | internal let maxIterations = 1000 18 | -------------------------------------------------------------------------------- /swiftVibrant/Filter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Filter.swift 3 | // swift-vibrant-ios 4 | // 5 | // Created by Bryce Dougherty on 5/3/20. 6 | // Copyright © 2020 Bryce Dougherty. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class Filter { 12 | public typealias filterFunction = (_ red: UInt8, _ green: UInt8, _ blue: UInt8, _ alpha: UInt8)->Bool 13 | 14 | var f: filterFunction 15 | var id: String 16 | 17 | public init(_ f: @escaping filterFunction) { 18 | self.f = f 19 | self.id = UUID().uuidString 20 | } 21 | 22 | private init(_ f: @escaping filterFunction, id: String) { 23 | self.f = f 24 | self.id = id 25 | } 26 | 27 | public static func combineFilters (filters: [Filter])->Filter? { 28 | if filters.count == 0 { return nil } 29 | let newFilterFunction:filterFunction = { r,g,b,a in 30 | if a == 0 { return false } 31 | for f in filters { 32 | if !f.f(r,g,b,a) { return false } 33 | } 34 | return true 35 | } 36 | return Filter(newFilterFunction) 37 | } 38 | 39 | public static let defaultFilter: Filter = Filter({r, g, b, a in 40 | return a >= 125 && !(r > 250 && g > 250 && b > 250) 41 | }, id: "default") 42 | } 43 | -------------------------------------------------------------------------------- /swiftVibrant/Generator.swift: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // default.swift 4 | // swift-vibrant 5 | // 6 | // Created by Bryce Dougherty on 4/30/20. 7 | // Copyright © 2020 Bryce Dougherty. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | 12 | public class Generator { 13 | 14 | public typealias generator = (_ swatches: [Swatch])->Palette 15 | 16 | public struct Options { 17 | var targetDarkLuma: Double = 0.26 18 | var maxDarkLuma: Double = 0.45 19 | var minLightLuma: Double = 0.55 20 | var targetLightLuma: Double = 0.74 21 | var minNormalLuma: Double = 0.3 22 | var targetNormalLuma: Double = 0.5 23 | var maxNormalLuma: Double = 0.7 24 | var targetMutesSaturation: Double = 0.3 25 | var maxMutesSaturation: Double = 0.4 26 | var targetVibrantSaturation: Double = 1.0 27 | var minVibrantSaturation: Double = 0.35 28 | var weightSaturation: Double = 3.0 29 | var weightLuma: Double = 6.5 30 | var weightPopulation: Double = 0.5 31 | } 32 | var options: Options 33 | init(options: Options) { 34 | self.options = options 35 | } 36 | 37 | 38 | public static let defaultGenerator:generator = Generator(options: Options()).generate 39 | 40 | private func generate(swatches: [Swatch])->Palette { 41 | let maxPopulation = findMaxPopulation(swatches: swatches) 42 | var palette = generateVariationColors(swatches: swatches, maxPopulation: maxPopulation, opts: options) 43 | palette = generateEmptySwatches(palette: palette, opts: options) 44 | return palette 45 | } 46 | 47 | func findMaxPopulation( swatches: [Swatch])->Int { 48 | var p: Int = 0 49 | swatches.forEach { (s: Swatch) in 50 | p = max(p, s.population) 51 | } 52 | return p 53 | } 54 | 55 | func isAlreadySelected (palette: Palette, s: Swatch)->Bool { 56 | return palette.Vibrant == s || 57 | palette.DarkVibrant == s || 58 | palette.LightVibrant == s || 59 | palette.Muted == s || 60 | palette.DarkMuted == s || 61 | palette.LightMuted == s 62 | } 63 | 64 | func createComparisonValue ( 65 | saturation: Double, targetSaturation: Double, 66 | luma: Double, targetLuma: Double, 67 | population: Int, maxPopulation: Int, opts: Options) -> Double { 68 | 69 | func weightedMean (values: Double...)->Double { 70 | var sum: Double = 0 71 | var weightSum: Double = 0 72 | var i = 0 73 | while i < values.count { 74 | let value = values[i] 75 | let weight = values[i + 1] 76 | sum += value * weight 77 | weightSum += weight 78 | i+=2 79 | } 80 | return sum / weightSum 81 | } 82 | 83 | func invertDiff (value: Double, targetValue: Double)->Double { 84 | return 1 - abs(value - targetValue) 85 | } 86 | 87 | 88 | return weightedMean( 89 | values: invertDiff(value: saturation, targetValue: targetSaturation), opts.weightSaturation, 90 | invertDiff(value: luma, targetValue: targetLuma), opts.weightLuma, 91 | Double(population) / Double(maxPopulation), opts.weightPopulation 92 | ) 93 | } 94 | 95 | func findColorVariation (palette: Palette, swatches: [Swatch], maxPopulation: Int, 96 | targetLuma: Double, 97 | minLuma: Double, 98 | maxLuma: Double, 99 | targetSaturation: Double, 100 | minSaturation: Double, 101 | maxSaturation: Double, 102 | opts: Options)->Swatch? { 103 | 104 | var max: Swatch? = nil 105 | var maxValue: Double = 0 106 | 107 | swatches.forEach({swatch in 108 | let (_,s,l) = swatch.hsl 109 | 110 | if (s >= minSaturation && s <= maxSaturation && 111 | l >= minLuma && l <= maxLuma && 112 | !isAlreadySelected(palette: palette, s: swatch) 113 | ) { 114 | let value = createComparisonValue(saturation: s, targetSaturation: targetSaturation, luma: l, targetLuma: targetLuma, population: swatch.population, maxPopulation: maxPopulation, opts: opts) 115 | if (max == nil || value > maxValue) { 116 | max = swatch 117 | maxValue = value 118 | } 119 | } 120 | }) 121 | return max 122 | } 123 | 124 | func generateVariationColors (swatches: [Swatch], maxPopulation: Int, opts: Options)->Palette { 125 | 126 | var palette = Palette() 127 | 128 | palette.Vibrant = findColorVariation(palette: palette, swatches: swatches, maxPopulation: maxPopulation, 129 | targetLuma: opts.targetNormalLuma, 130 | minLuma: opts.minNormalLuma, 131 | maxLuma: opts.maxNormalLuma, 132 | targetSaturation: opts.targetVibrantSaturation, 133 | minSaturation: opts.minVibrantSaturation, 134 | maxSaturation: 1, 135 | opts: opts 136 | ) 137 | 138 | palette.LightVibrant = findColorVariation(palette: palette, swatches: swatches, maxPopulation: maxPopulation, 139 | targetLuma: opts.targetLightLuma, 140 | minLuma: opts.minLightLuma, 141 | maxLuma: 1, 142 | targetSaturation: opts.targetVibrantSaturation, 143 | minSaturation: opts.minVibrantSaturation, 144 | maxSaturation: 1, 145 | opts: opts 146 | ) 147 | 148 | palette.DarkVibrant = findColorVariation(palette: palette, swatches: swatches, maxPopulation: maxPopulation, 149 | targetLuma: opts.targetDarkLuma, 150 | minLuma: 0, 151 | maxLuma: opts.maxDarkLuma, 152 | targetSaturation: opts.targetVibrantSaturation, 153 | minSaturation: opts.minVibrantSaturation, 154 | maxSaturation: 1, 155 | opts: opts 156 | ) 157 | 158 | palette.Muted = findColorVariation(palette: palette, swatches: swatches, maxPopulation: maxPopulation, 159 | targetLuma: opts.targetNormalLuma, 160 | minLuma: opts.minNormalLuma, 161 | maxLuma: opts.maxNormalLuma, 162 | targetSaturation: opts.targetMutesSaturation, 163 | minSaturation: 0, 164 | maxSaturation: opts.maxMutesSaturation, 165 | opts: opts 166 | ) 167 | 168 | palette.LightMuted = findColorVariation(palette: palette, swatches: swatches, maxPopulation: maxPopulation, 169 | targetLuma: opts.targetLightLuma, 170 | minLuma: opts.minLightLuma, 171 | maxLuma: 1, 172 | targetSaturation: opts.targetMutesSaturation, 173 | minSaturation: 0, 174 | maxSaturation: opts.maxMutesSaturation, 175 | opts: opts 176 | ) 177 | 178 | palette.DarkMuted = findColorVariation(palette: palette, swatches: swatches, maxPopulation: maxPopulation, 179 | targetLuma: opts.targetDarkLuma, 180 | minLuma: 0, 181 | maxLuma: opts.maxDarkLuma, 182 | targetSaturation: opts.targetMutesSaturation, 183 | minSaturation: 0, 184 | maxSaturation: opts.maxMutesSaturation, 185 | opts: opts 186 | ) 187 | return palette 188 | 189 | } 190 | // 191 | func generateEmptySwatches (palette: Palette, opts: Options)->Palette { 192 | 193 | var palette = palette 194 | //function _generateEmptySwatches (palette: Palette, maxPopulation: number, opts: DefaultGeneratorOptions): void { 195 | if (palette.Vibrant == nil && palette.DarkVibrant == nil && palette.LightVibrant == nil) { 196 | if (palette.DarkVibrant == nil && palette.DarkMuted != nil) { 197 | var (h, s, l) = palette.DarkMuted!.hsl 198 | l = opts.targetDarkLuma 199 | palette.DarkVibrant = Swatch(hslToRgb(h, s, l), 0) 200 | } 201 | if (palette.LightVibrant == nil && palette.LightMuted != nil) { 202 | var (h, s, l) = palette.LightMuted!.hsl 203 | l = opts.targetDarkLuma 204 | palette.DarkVibrant = Swatch(hslToRgb(h, s, l), 0) 205 | } 206 | } 207 | if (palette.Vibrant == nil && palette.DarkVibrant != nil) { 208 | var (h, s, l) = palette.DarkVibrant!.hsl 209 | l = opts.targetNormalLuma 210 | palette.Vibrant = Swatch(hslToRgb(h, s, l), 0) 211 | } else if (palette.Vibrant == nil && palette.LightVibrant != nil) { 212 | var (h, s, l) = palette.LightVibrant!.hsl 213 | l = opts.targetNormalLuma 214 | palette.Vibrant = Swatch(hslToRgb(h, s, l), 0) 215 | } 216 | if (palette.DarkVibrant == nil && palette.Vibrant != nil) { 217 | var (h, s, l) = palette.Vibrant!.hsl 218 | l = opts.targetDarkLuma 219 | palette.DarkVibrant = Swatch(hslToRgb(h, s, l), 0) 220 | } 221 | if (palette.LightVibrant == nil && palette.Vibrant != nil) { 222 | var (h, s, l) = palette.Vibrant!.hsl 223 | l = opts.targetLightLuma 224 | palette.LightVibrant = Swatch(hslToRgb(h, s, l), 0) 225 | } 226 | if (palette.Muted == nil && palette.Vibrant != nil) { 227 | var (h, s, l) = palette.Vibrant!.hsl 228 | l = opts.targetMutesSaturation 229 | palette.Muted = Swatch(hslToRgb(h, s, l), 0) 230 | } 231 | if (palette.DarkMuted == nil && palette.DarkVibrant != nil) { 232 | var (h, s, l) = palette.DarkVibrant!.hsl 233 | l = opts.targetMutesSaturation 234 | palette.DarkMuted = Swatch(hslToRgb(h, s, l), 0) 235 | } 236 | if (palette.LightMuted == nil && palette.LightVibrant != nil) { 237 | var (h, s, l) = palette.LightVibrant!.hsl 238 | l = opts.targetMutesSaturation 239 | palette.LightMuted = Swatch(hslToRgb(h, s, l), 0) 240 | } 241 | return palette 242 | } 243 | } 244 | /** 245 | ```` 246 | const DefaultGenerator: Generator = (swatches: Array, opts?: DefaultGeneratorOptions): Palette => { 247 | opts = defaults({}, opts, DefaultOpts) 248 | let maxPopulation = _findMaxPopulation(swatches) 249 | 250 | let palette = _generateVariationColors(swatches, maxPopulation, opts) 251 | _generateEmptySwatches(palette, maxPopulation, opts) 252 | 253 | return palette 254 | } 255 | ```` 256 | */ 257 | -------------------------------------------------------------------------------- /swiftVibrant/Image.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Image.swift 3 | // swift-vibrant-ios 4 | // 5 | // Created by Bryce Dougherty on 5/3/20. 6 | // Copyright © 2020 Bryce Dougherty. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public class Image { 13 | var image: UIImage 14 | 15 | init(image: UIImage) { 16 | self.image = image 17 | } 18 | 19 | func applyFilter(_ filter: Filter)->[UInt8] { 20 | guard let imageData = self.getImageData() else { 21 | return [] 22 | } 23 | var pixels = imageData 24 | let n = pixels.count / 4 25 | var offset: Int 26 | var r, g, b, a: UInt8 27 | 28 | for i in 0..[UInt8]? { 43 | return Image.makeBytes(from: self.image) 44 | } 45 | 46 | func scaleTo(size maxSize: CGFloat?, quality: Int) { 47 | let width = image.size.width 48 | let height = image.size.height 49 | 50 | var ratio:CGFloat = 1.0 51 | if maxSize != nil && maxSize! > 0 { 52 | let maxSide = max(width, height) 53 | if maxSide > CGFloat(maxSize!) { 54 | ratio = CGFloat(maxSize!) / maxSide 55 | } 56 | } else { 57 | ratio = 1 / CGFloat(quality) 58 | } 59 | if ratio < 1 { 60 | self.scale(by: ratio) 61 | } 62 | } 63 | 64 | func scale(by scale: CGFloat) { 65 | self.image = Image.scaleImage(image: self.image, by: scale) 66 | } 67 | 68 | private static func scaleImage(image: UIImage, by scale: CGFloat)->UIImage { 69 | if scale == 1 { return image } 70 | 71 | let imageRef = image.cgImage! 72 | let width = imageRef.width 73 | let height = imageRef.height 74 | 75 | var bounds = CGSize(width: width, height: height) 76 | 77 | bounds.width = CGFloat(width) * scale 78 | bounds.height = CGFloat(height) * scale 79 | 80 | UIGraphicsBeginImageContext(bounds) 81 | image.draw(in: CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height)) 82 | let imageCopy = UIGraphicsGetImageFromCurrentImageContext() 83 | UIGraphicsEndImageContext() 84 | 85 | return imageCopy ?? image 86 | } 87 | 88 | private static func makeBytes(from image: UIImage) -> [UInt8]? { 89 | guard let cgImage = image.cgImage else { 90 | return nil 91 | } 92 | if isCompatibleImage(cgImage) { 93 | return makeBytesFromCompatibleImage(cgImage) 94 | } else { 95 | return makeBytesFromIncompatibleImage(cgImage) 96 | } 97 | } 98 | 99 | private static func isCompatibleImage(_ cgImage: CGImage) -> Bool { 100 | guard let colorSpace = cgImage.colorSpace else { 101 | return false 102 | } 103 | if colorSpace.model != .rgb { 104 | return false 105 | } 106 | let bitmapInfo = cgImage.bitmapInfo 107 | let alpha = bitmapInfo.rawValue & CGBitmapInfo.alphaInfoMask.rawValue 108 | let alphaRequirement = (alpha == CGImageAlphaInfo.noneSkipLast.rawValue || alpha == CGImageAlphaInfo.last.rawValue) 109 | let byteOrder = bitmapInfo.rawValue & CGBitmapInfo.byteOrderMask.rawValue 110 | let byteOrderRequirement = (byteOrder == CGBitmapInfo.byteOrder32Little.rawValue) 111 | if !(alphaRequirement && byteOrderRequirement) { 112 | return false 113 | } 114 | if cgImage.bitsPerComponent != 8 { 115 | return false 116 | } 117 | if cgImage.bitsPerPixel != 32 { 118 | return false 119 | } 120 | if cgImage.bytesPerRow != cgImage.width * 4 { 121 | return false 122 | } 123 | return true 124 | } 125 | private static func makeBytesFromCompatibleImage(_ image: CGImage) -> [UInt8]? { 126 | guard let dataProvider = image.dataProvider else { 127 | return nil 128 | } 129 | guard let data = dataProvider.data else { 130 | return nil 131 | } 132 | let length = CFDataGetLength(data) 133 | var rawData = [UInt8](repeating: 0, count: length) 134 | CFDataGetBytes(data, CFRange(location: 0, length: length), &rawData) 135 | return rawData 136 | } 137 | 138 | private static func makeBytesFromIncompatibleImage(_ image: CGImage) -> [UInt8]? { 139 | let width = image.width 140 | let height = image.height 141 | var rawData = [UInt8](repeating: 0, count: width * height * 4) 142 | guard let context = CGContext( 143 | data: &rawData, 144 | width: width, 145 | height: height, 146 | bitsPerComponent: 8, 147 | bytesPerRow: 4 * width, 148 | space: CGColorSpaceCreateDeviceRGB(), 149 | bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue | CGBitmapInfo.byteOrder32Little.rawValue) else { 150 | return nil 151 | } 152 | context.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height)) 153 | return rawData 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /swiftVibrant/MMCQ.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MMCQ.swift 3 | // ColorThiefSwift 4 | // 5 | // Created by Kazuki Ohara on 2017/02/11. 6 | // Copyright © 2019 Kazuki Ohara. All rights reserved. 7 | // 8 | // License 9 | // ------- 10 | // MIT License 11 | // https://github.com/yamoridon/ColorThiefSwift/blob/master/LICENSE 12 | // 13 | // Thanks 14 | // ------ 15 | // Lokesh Dhakar - for the original Color Thief JavaScript version 16 | // http://lokeshdhakar.com/projects/color-thief/ 17 | // Sven Woltmann - for the fast Java Implementation 18 | // https://github.com/SvenWoltmann/color-thief-java 19 | 20 | import Foundation 21 | import UIKit 22 | 23 | /// MMCQ (modified median cut quantization) algorithm from 24 | /// the Leptonica library (http://www.leptonica.com/). 25 | 26 | /// Get reduced-space color index for a pixel. 27 | /// 28 | /// - Parameters: 29 | /// - red: the red value 30 | /// - green: the green value 31 | /// - blue: the blue value 32 | /// - Returns: the color index 33 | 34 | 35 | public struct Color { 36 | public var r: UInt8 37 | public var g: UInt8 38 | public var b: UInt8 39 | 40 | init(r: UInt8, g: UInt8, b: UInt8) { 41 | self.r = r 42 | self.g = g 43 | self.b = b 44 | } 45 | 46 | public func makeUIColor() -> UIColor { 47 | return UIColor(red: CGFloat(r) / CGFloat(255), green: CGFloat(g) / CGFloat(255), blue: CGFloat(b) / CGFloat(255), alpha: CGFloat(1)) 48 | } 49 | } 50 | 51 | enum ColorChannel { 52 | case r 53 | case g 54 | case b 55 | } 56 | 57 | /// 3D color space box. 58 | 59 | 60 | /// Color map. 61 | open class ColorMap { 62 | 63 | var vboxes = [VBox]() 64 | 65 | func push(_ vbox: VBox) { 66 | vboxes.append(vbox) 67 | } 68 | 69 | open func makePalette() -> [Color] { 70 | return vboxes.map { $0.getAverage() } 71 | } 72 | 73 | open func makeNearestColor(to color: Color) -> Color { 74 | var nearestDistance = Int.max 75 | var nearestColor = Color(r: 0, g: 0, b: 0) 76 | 77 | for vbox in vboxes { 78 | let vbColor = vbox.getAverage() 79 | let dr = abs(Int(color.r) - Int(vbColor.r)) 80 | let dg = abs(Int(color.g) - Int(vbColor.g)) 81 | let db = abs(Int(color.b) - Int(vbColor.b)) 82 | let distance = dr + dg + db 83 | if distance < nearestDistance { 84 | nearestDistance = distance 85 | nearestColor = vbColor 86 | } 87 | } 88 | 89 | return nearestColor 90 | } 91 | } 92 | 93 | /// Histo (1-d array, giving the number of pixels in each quantized region of color space), or null on error. 94 | internal func makeHistogramAndVBox(from pixels: [UInt8], quality: Int, ignoreWhite: Bool) -> ([Int], VBox) { 95 | var histogram = [Int](repeating: 0, count: histogramSize) 96 | var rMin = UInt8.max 97 | var rMax = UInt8.min 98 | var gMin = UInt8.max 99 | var gMax = UInt8.min 100 | var bMin = UInt8.max 101 | var bMax = UInt8.min 102 | 103 | let pixelCount = pixels.count / 4 104 | for i in stride(from: 0, to: pixelCount, by: quality) { 105 | let a = pixels[i * 4 + 0] 106 | let b = pixels[i * 4 + 1] 107 | let g = pixels[i * 4 + 2] 108 | let r = pixels[i * 4 + 3] 109 | 110 | // If pixel is not mostly opaque or white 111 | guard a >= 125 && !(ignoreWhite && r > 250 && g > 250 && b > 250) else { 112 | continue 113 | } 114 | 115 | let shiftedR = r >> UInt8(rightShift) 116 | let shiftedG = g >> UInt8(rightShift) 117 | let shiftedB = b >> UInt8(rightShift) 118 | 119 | // find min/max 120 | rMin = min(rMin, shiftedR) 121 | rMax = max(rMax, shiftedR) 122 | gMin = min(gMin, shiftedG) 123 | gMax = max(gMax, shiftedG) 124 | bMin = min(bMin, shiftedB) 125 | bMax = max(bMax, shiftedB) 126 | 127 | // increment histgram 128 | let index = makeColorIndexOf(red: Int(shiftedR), green: Int(shiftedG), blue: Int(shiftedB)) 129 | histogram[index] += 1 130 | } 131 | 132 | let vbox = VBox(rMin: rMin, rMax: rMax, gMin: gMin, gMax: gMax, bMin: bMin, bMax: bMax, histogram: histogram) 133 | return (histogram, vbox) 134 | } 135 | -------------------------------------------------------------------------------- /swiftVibrant/Quantizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Quantize.swift 3 | // swift-vibrant 4 | // 5 | // Created by Bryce Dougherty on 5/3/20. 6 | // Copyright © 2020 Bryce Dougherty. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class Quantizer { 12 | public typealias quantizer = (_ pixels: [UInt8], _ options: Vibrant.Options)->[Swatch] 13 | 14 | public static let defaultQuantizer: quantizer = Quantizer().quantize 15 | 16 | private func quantize(pixels: [UInt8], options: Vibrant.Options)->[Swatch] { 17 | let quality = options.quality 18 | let colorcount = options.colorCount 19 | return Quantizer.vibrantQuantizer(pixels: pixels, quality: quality, colorCount: colorcount) 20 | } 21 | 22 | private static func splitBoxes (_ pq: inout [VBox], _ target: Int, hist: [Int]) { 23 | var lastSize = pq.count 24 | while pq.count < target { 25 | let vbox = pq.popLast() 26 | 27 | if (vbox != nil && vbox!.getCount() > 0) { 28 | let vboxes = applyMedianCut(with: hist, vbox: vbox!) 29 | let vbox1 = vboxes.count > 0 ? vboxes[0] : nil 30 | let vbox2 = vboxes.count > 1 ? vboxes[1] : nil 31 | pq.append(vbox1!) 32 | if vbox2 != nil && vbox2!.getCount() > 0 { pq.append(vbox2!) } 33 | 34 | if pq.count == lastSize { 35 | break 36 | } else { 37 | lastSize = pq.count 38 | } 39 | } else { 40 | break 41 | } 42 | } 43 | } 44 | 45 | static func vibrantQuantizer(pixels: [UInt8], quality: Int, colorCount: Int)->[Swatch] { 46 | let (hist, vbox) = makeHistogramAndVBox(from: pixels, quality: quality, ignoreWhite: false) 47 | var pq = [vbox] 48 | splitBoxes(&pq, Int(fractionByPopulation * Double(colorCount)), hist: hist) 49 | pq.sort { (a, b) -> Bool in 50 | a.getCount() * a.getVolume() > b.getCount() * b.getVolume() 51 | } 52 | splitBoxes(&pq, colorCount - pq.count, hist: hist) 53 | return generateSwatches(pq) 54 | } 55 | 56 | private static func generateSwatches (_ pq: [VBox])->[Swatch] { 57 | return pq.map { (box) in 58 | let color = box.rgb() 59 | return Swatch(color, box.getCount()) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /swiftVibrant/Util.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Util.swift 3 | // swift-vibrant 4 | // 5 | // Created by Bryce Dougherty on 5/3/20. 6 | // Copyright © 2020 Bryce Dougherty. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | internal func applyMedianCut(with histogram: [Int], vbox: VBox) -> [VBox] { 12 | guard vbox.getCount() != 0 else { 13 | return [] 14 | } 15 | 16 | // only one pixel, no split 17 | guard vbox.getCount() != 1 else { 18 | return [vbox] 19 | } 20 | 21 | // Find the partial sum arrays along the selected axis. 22 | var total = 0 23 | var partialSum = [Int](repeating: -1, count: vboxLength) // -1 = not set / 0 = 0 24 | 25 | let axis = vbox.widestColorChannel() 26 | switch axis { 27 | case .r: 28 | for i in vbox.rRange { 29 | var sum = 0 30 | for j in vbox.gRange { 31 | for k in vbox.bRange { 32 | let index = makeColorIndexOf(red: i, green: j, blue: k) 33 | sum += histogram[index] 34 | } 35 | } 36 | total += sum 37 | partialSum[i] = total 38 | } 39 | case .g: 40 | for i in vbox.gRange { 41 | var sum = 0 42 | for j in vbox.rRange { 43 | for k in vbox.bRange { 44 | let index = makeColorIndexOf(red: j, green: i, blue: k) 45 | sum += histogram[index] 46 | } 47 | } 48 | total += sum 49 | partialSum[i] = total 50 | } 51 | case .b: 52 | for i in vbox.bRange { 53 | var sum = 0 54 | for j in vbox.rRange { 55 | for k in vbox.gRange { 56 | let index = makeColorIndexOf(red: j, green: k, blue: i) 57 | sum += histogram[index] 58 | } 59 | } 60 | total += sum 61 | partialSum[i] = total 62 | } 63 | } 64 | 65 | var lookAheadSum = [Int](repeating: -1, count: vboxLength) // -1 = not set / 0 = 0 66 | for (i, sum) in partialSum.enumerated() where sum != -1 { 67 | lookAheadSum[i] = total - sum 68 | } 69 | 70 | return cut(by: axis, vbox: vbox, partialSum: partialSum, lookAheadSum: lookAheadSum, total: total) 71 | } 72 | 73 | internal func cut(by axis: ColorChannel, vbox: VBox, partialSum: [Int], lookAheadSum: [Int], total: Int) -> [VBox] { 74 | let vboxMin: Int 75 | let vboxMax: Int 76 | 77 | switch axis { 78 | case .r: 79 | vboxMin = Int(vbox.rMin) 80 | vboxMax = Int(vbox.rMax) 81 | case .g: 82 | vboxMin = Int(vbox.gMin) 83 | vboxMax = Int(vbox.gMax) 84 | case .b: 85 | vboxMin = Int(vbox.bMin) 86 | vboxMax = Int(vbox.bMax) 87 | } 88 | 89 | for i in vboxMin ... vboxMax where partialSum[i] > total / 2 { 90 | let vbox1 = VBox(vbox: vbox) 91 | let vbox2 = VBox(vbox: vbox) 92 | 93 | let left = i - vboxMin 94 | let right = vboxMax - i 95 | 96 | var d2: Int 97 | if left <= right { 98 | d2 = min(vboxMax - 1, i + right / 2) 99 | } else { 100 | // 2.0 and cast to int is necessary to have the same 101 | // behaviour as in JavaScript 102 | d2 = max(vboxMin, Int(Double(i - 1) - Double(left) / 2.0)) 103 | } 104 | 105 | // avoid 0-count 106 | while d2 < 0 || partialSum[d2] <= 0 { 107 | d2 += 1 108 | } 109 | var count2 = lookAheadSum[d2] 110 | while count2 == 0 && d2 > 0 && partialSum[d2 - 1] > 0 { 111 | d2 -= 1 112 | count2 = lookAheadSum[d2] 113 | } 114 | 115 | // set dimensions 116 | switch axis { 117 | case .r: 118 | vbox1.rMax = UInt8(d2) 119 | vbox2.rMin = UInt8(d2 + 1) 120 | case .g: 121 | vbox1.gMax = UInt8(d2) 122 | vbox2.gMin = UInt8(d2 + 1) 123 | case .b: 124 | vbox1.bMax = UInt8(d2) 125 | vbox2.bMin = UInt8(d2 + 1) 126 | } 127 | 128 | return [vbox1, vbox2] 129 | } 130 | 131 | fatalError("VBox can't be cut") 132 | } 133 | 134 | internal func iterate(over queue: inout [VBox], comparator: (VBox, VBox) -> Bool, target: Int, histogram: [Int]) { 135 | var color = 1 136 | 137 | for _ in 0 ..< maxIterations { 138 | guard let vbox = queue.last else { 139 | return 140 | } 141 | 142 | if vbox.getCount() == 0 { 143 | queue.sort(by: comparator) 144 | continue 145 | } 146 | queue.removeLast() 147 | 148 | // do the cut 149 | let vboxes = applyMedianCut(with: histogram, vbox: vbox) 150 | queue.append(vboxes[0]) 151 | if vboxes.count == 2 { 152 | queue.append(vboxes[1]) 153 | color += 1 154 | } 155 | queue.sort(by: comparator) 156 | 157 | if color >= target { 158 | return 159 | } 160 | } 161 | } 162 | 163 | internal func compareByCount(_ a: VBox, _ b: VBox) -> Bool { 164 | return a.getCount() < b.getCount() 165 | } 166 | 167 | internal func compareByProduct(_ a: VBox, _ b: VBox) -> Bool { 168 | let aCount = a.getCount() 169 | let bCount = b.getCount() 170 | let aVolume = a.getVolume() 171 | let bVolume = b.getVolume() 172 | 173 | if aCount == bCount { 174 | // If count is 0 for both (or the same), sort by volume 175 | return aVolume < bVolume 176 | } else { 177 | // Otherwise sort by products 178 | let aProduct = Int64(aCount) * Int64(aVolume) 179 | let bProduct = Int64(bCount) * Int64(bVolume) 180 | return aProduct < bProduct 181 | } 182 | } 183 | 184 | func makeColorIndexOf(red: Int, green: Int, blue: Int) -> Int { 185 | return (red << (2 * signalBits)) + (green << signalBits) + blue 186 | } 187 | 188 | func apply(_ fn: (T) -> V, _ args: T) -> V { 189 | return fn(args) 190 | } 191 | -------------------------------------------------------------------------------- /swiftVibrant/Vbox.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Vbox.swift 3 | // swift-vibrant 4 | // 5 | // Created by Bryce Dougherty on 5/3/20. 6 | // Copyright © 2020 Bryce Dougherty. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class VBox { 12 | 13 | var rMin: UInt8 14 | var rMax: UInt8 15 | var gMin: UInt8 16 | var gMax: UInt8 17 | var bMin: UInt8 18 | var bMax: UInt8 19 | 20 | private let histogram: [Int] 21 | 22 | private var average: Color? 23 | private var volume: Int? 24 | private var count: Int? 25 | 26 | init(rMin: UInt8, rMax: UInt8, gMin: UInt8, gMax: UInt8, bMin: UInt8, bMax: UInt8, histogram: [Int]) { 27 | self.rMin = rMin 28 | self.rMax = rMax 29 | self.gMin = gMin 30 | self.gMax = gMax 31 | self.bMin = bMin 32 | self.bMax = bMax 33 | self.histogram = histogram 34 | } 35 | 36 | init(vbox: VBox) { 37 | self.rMin = vbox.rMin 38 | self.rMax = vbox.rMax 39 | self.gMin = vbox.gMin 40 | self.gMax = vbox.gMax 41 | self.bMin = vbox.bMin 42 | self.bMax = vbox.bMax 43 | self.histogram = vbox.histogram 44 | } 45 | 46 | func makeRange(min: UInt8, max: UInt8) -> CountableRange { 47 | if min <= max { 48 | return Int(min) ..< Int(max + 1) 49 | } else { 50 | return Int(max) ..< Int(max) 51 | } 52 | } 53 | 54 | var rRange: CountableRange { return makeRange(min: rMin, max: rMax) } 55 | var gRange: CountableRange { return makeRange(min: gMin, max: gMax) } 56 | var bRange: CountableRange { return makeRange(min: bMin, max: bMax) } 57 | 58 | /// Get 3 dimensional volume of the color space 59 | /// 60 | /// - Parameter force: force recalculate 61 | /// - Returns: the volume 62 | func getVolume(forceRecalculate force: Bool = false) -> Int { 63 | if let volume = volume, !force { 64 | return volume 65 | } else { 66 | let volume = (Int(rMax) - Int(rMin) + 1) * (Int(gMax) - Int(gMin) + 1) * (Int(bMax) - Int(bMin) + 1) 67 | self.volume = volume 68 | return volume 69 | } 70 | } 71 | 72 | /// Get total count of histogram samples 73 | /// 74 | /// - Parameter force: force recalculate 75 | /// - Returns: the volume 76 | func getCount(forceRecalculate force: Bool = false) -> Int { 77 | if let count = count, !force { 78 | return count 79 | } else { 80 | var count = 0 81 | for i in rRange { 82 | for j in gRange { 83 | for k in bRange { 84 | let index = makeColorIndexOf(red: i, green: j, blue: k) 85 | count += histogram[index] 86 | } 87 | } 88 | } 89 | self.count = count 90 | return count 91 | } 92 | } 93 | 94 | func getAverage(forceRecalculate force: Bool = false) -> Color { 95 | if let average = average, !force { 96 | return average 97 | } else { 98 | var ntot = 0 99 | 100 | var rSum = 0 101 | var gSum = 0 102 | var bSum = 0 103 | 104 | for i in rRange { 105 | for j in gRange { 106 | for k in bRange { 107 | let index = makeColorIndexOf(red: i, green: j, blue: k) 108 | let hval = histogram[index] 109 | ntot += hval 110 | rSum += Int(Double(hval) * (Double(i) + 0.5) * Double(multiplier)) 111 | gSum += Int(Double(hval) * (Double(j) + 0.5) * Double(multiplier)) 112 | bSum += Int(Double(hval) * (Double(k) + 0.5) * Double(multiplier)) 113 | } 114 | } 115 | } 116 | 117 | let average: Color 118 | if ntot > 0 { 119 | let r = UInt8(rSum / ntot) 120 | let g = UInt8(gSum / ntot) 121 | let b = UInt8(bSum / ntot) 122 | average = Color(r: r, g: g, b: b) 123 | } else { 124 | let r = UInt8(min(multiplier * (Int(rMin) + Int(rMax) + 1) / 2, 255)) 125 | let g = UInt8(min(multiplier * (Int(gMin) + Int(gMax) + 1) / 2, 255)) 126 | let b = UInt8(min(multiplier * (Int(bMin) + Int(bMax) + 1) / 2, 255)) 127 | average = Color(r: r, g: g, b: b) 128 | } 129 | 130 | self.average = average 131 | return average 132 | } 133 | } 134 | 135 | 136 | func rgb() -> RGB { 137 | let color = self.getAverage() 138 | return (color.r, color.g, color.b) 139 | } 140 | 141 | func widestColorChannel() -> ColorChannel { 142 | let rWidth = rMax - rMin 143 | let gWidth = gMax - gMin 144 | let bWidth = bMax - bMin 145 | switch max(rWidth, gWidth, bWidth) { 146 | case rWidth: 147 | return .r 148 | case gWidth: 149 | return .g 150 | default: 151 | return .b 152 | } 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /swiftVibrant/Vibrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Vibrant.swift 3 | // swift-vibrant-ios 4 | // 5 | // Created by Bryce Dougherty on 5/3/20. 6 | // Copyright © 2020 Bryce Dougherty. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public class Vibrant { 13 | 14 | public struct Options { 15 | var colorCount: Int = 64 16 | 17 | var quality: Int = 5 18 | 19 | var quantizer: Quantizer.quantizer = Quantizer.defaultQuantizer 20 | 21 | var generator: Generator.generator = Generator.defaultGenerator 22 | 23 | var maxDimension: CGFloat? 24 | 25 | var filters: [Filter] = [Filter.defaultFilter] 26 | 27 | fileprivate var combinedFilter: Filter? 28 | } 29 | 30 | public static func from( _ src: UIImage)->Builder { 31 | return Builder(src) 32 | } 33 | 34 | var opts: Options 35 | var src: UIImage 36 | 37 | private var _palette: Palette? 38 | public var palette: Palette? { _palette } 39 | 40 | public init(src: UIImage, opts: Options?) { 41 | self.src = src 42 | self.opts = opts ?? Options() 43 | self.opts.combinedFilter = Filter.combineFilters(filters: self.opts.filters) 44 | } 45 | 46 | static func process(image: Image, opts: Options)->Palette { 47 | let quantizer = opts.quantizer 48 | let generator = opts.generator 49 | let combinedFilter = opts.combinedFilter! 50 | let maxDimension = opts.maxDimension 51 | 52 | image.scaleTo(size: maxDimension, quality: opts.quality) 53 | 54 | 55 | let imageData = image.applyFilter(combinedFilter) 56 | let swatches = quantizer(imageData, opts) 57 | let colors = Swatch.applyFilter(colors: swatches, filter: combinedFilter) 58 | let palette = generator(colors) 59 | return palette 60 | } 61 | 62 | public func getPalette(_ cb: @escaping Callback) { 63 | DispatchQueue.init(label: "colorProcessor", qos: .background).async { 64 | let palette = self.getPalette() 65 | DispatchQueue.main.async { 66 | cb(palette) 67 | } 68 | } 69 | } 70 | 71 | public func getPalette()->Palette { 72 | let image = Image(image: self.src) 73 | let palette = Vibrant.process(image: image, opts: self.opts) 74 | self._palette = palette 75 | return palette 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /swiftVibrant/VibrantColors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VibrantColors.swift 3 | // swift-vibrant-ios 4 | // 5 | // Created by Bryce Dougherty on 5/3/20. 6 | // Copyright © 2020 Bryce Dougherty. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public typealias Vec3 = (T, T, T) 12 | public typealias RGB = (r: UInt8, g: UInt8, b: UInt8) 13 | public typealias HSL = (h: Double, s: Double, l: Double) 14 | public typealias XYZ = (x: Double, y: Double, z: Double) 15 | public typealias LAB = (L: Double, a: Double, b: Double) 16 | 17 | 18 | //export type Vec3 = [Double, Double, Double] 19 | // 20 | 21 | public struct Palette { 22 | public var Vibrant: Swatch? 23 | public var Muted: Swatch? 24 | public var DarkVibrant: Swatch? 25 | public var DarkMuted: Swatch? 26 | public var LightVibrant: Swatch? 27 | public var LightMuted: Swatch? 28 | } 29 | 30 | public class Swatch: Equatable { 31 | 32 | private var _hsl: HSL? 33 | 34 | private var _rgb: RGB 35 | 36 | private var _yiq: Double? 37 | 38 | private var _population: Int 39 | 40 | private var _hex: String? 41 | 42 | private var _uiColor: UIColor? 43 | 44 | var r: UInt8 { self._rgb.r } 45 | 46 | var g: UInt8 { self._rgb.g } 47 | 48 | var b: UInt8 { self._rgb.b } 49 | 50 | var rgb: RGB { self._rgb } 51 | 52 | public var hsl: HSL { 53 | if self._hsl == nil { 54 | let rgb = self._rgb 55 | self._hsl = apply(rgbToHsl, rgb) 56 | } 57 | return self._hsl! 58 | } 59 | 60 | public var hex: String { 61 | if self._hex == nil { 62 | let rgb = self._rgb 63 | self._hex = apply(rgbToHex, rgb) 64 | } 65 | return self._hex! 66 | } 67 | 68 | 69 | public var uiColor: UIColor { 70 | if self._uiColor == nil { 71 | let rgb = self._rgb 72 | self._uiColor = apply(rgbToUIColor, rgb) 73 | } 74 | return self._uiColor! 75 | } 76 | 77 | static func applyFilter(colors: [Swatch], filter: Filter)->[Swatch] { 78 | var colors = colors 79 | colors = colors.filter { (swatch) -> Bool in 80 | let r = swatch.r 81 | let g = swatch.g 82 | let b = swatch.b 83 | return filter.f(r, g, b, 255) 84 | } 85 | return colors 86 | } 87 | 88 | public var population: Int { self._population } 89 | 90 | 91 | func toDict()->[String: Any] { 92 | return [ 93 | "rgb": self.rgb, 94 | "population": self.population 95 | ] 96 | } 97 | 98 | var toJSON = toDict 99 | 100 | private func getYiq()->Double { 101 | if self._yiq == nil { 102 | let (r,g,b) = self._rgb 103 | let mr = Int(r) * 299 104 | let mg = Int(g) * 598 105 | let mb = Int(b) * 114 106 | let mult = mr + mg + mb 107 | self._yiq = Double(mult) / 1000 108 | } 109 | return self._yiq! 110 | } 111 | 112 | private var _titleTextColor: UIColor? 113 | 114 | private var _bodyTextColor: UIColor? 115 | 116 | public var titleTextColor: UIColor { 117 | if self._titleTextColor == nil { 118 | self._titleTextColor = self.getYiq() < 200 ? .white : .black 119 | } 120 | return self._titleTextColor! 121 | } 122 | 123 | public var bodyTextColor: UIColor { 124 | if self._bodyTextColor == nil { 125 | self._bodyTextColor = self.getYiq() < 150 ? .white : .black 126 | } 127 | return self._bodyTextColor! 128 | } 129 | 130 | public func getTitleTextColor()->UIColor { 131 | return self.titleTextColor 132 | } 133 | 134 | public func getBodyTextColor()->UIColor { 135 | return self.bodyTextColor 136 | } 137 | 138 | public static func == (lhs: Swatch, rhs: Swatch) -> Bool { 139 | return lhs.rgb == rhs.rgb 140 | } 141 | 142 | init(_ rgb: RGB, _ population: Int) { 143 | self._rgb = rgb 144 | self._population = population 145 | } 146 | 147 | 148 | } 149 | --------------------------------------------------------------------------------