├── .travis.yml ├── ColorArtwork.playground ├── Contents.swift ├── Resources │ ├── Chuck.jpg │ ├── Discovery.jpg │ ├── Ghost Stories.jpg │ ├── I'm With You.jpg │ ├── Making Mirrors.jpg │ ├── My Beautiful Dark Twisted Fantasy.jpg │ ├── The Same Old Blood Rush With a New Touch.jpg │ └── Too Weird To Live, Too Rare To Die!.jpg ├── Sources │ └── ColorArtworkPreview.swift └── contents.xcplayground ├── ColorArtwork.xcodeproj ├── ColorArtworkTests_Info.plist ├── ColorArtwork_Info.plist ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── ColorArtwork-Package.xcscheme ├── ColorArtwork.xcworkspace └── contents.xcworkspacedata ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── ColorArtwork │ ├── ColorArtwork.swift │ ├── Image+ColorArtwork.swift │ └── RGBColor.swift ├── Tests └── ColorArtworkTests │ ├── ColorArtworkTests.swift │ └── Stadium Arcadium.jpg └── docs └── img └── preview.png /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode9 3 | 4 | env: 5 | - ACTION=test PLATFORM=macOS DESTINATION='platform=OS X' 6 | - ACTION=build PLATFORM=iOS DESTINATION='platform=iOS Simulator,name=iPhone 6S' 7 | - ACTION=build PLATFORM=tvOS DESTINATION='platform=tvOS Simulator,name=Apple TV 1080p' 8 | - ACTION=build PLATFORM=watchOS DESTINATION='platform=watchOS Simulator,name=Apple Watch - 42mm' 9 | 10 | script: 11 | - set -o pipefail && xcodebuild -workspace ColorArtwork.xcworkspace -scheme "ColorArtwork-Package" -destination "$DESTINATION" $ACTION | xcpretty 12 | -------------------------------------------------------------------------------- /ColorArtwork.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import ColorArtwork 3 | 4 | let albums = [ 5 | (#imageLiteral(resourceName: "Ghost Stories.jpg"), "Ghost Stories", "Coldplay", "Alternative • 2014"), 6 | (#imageLiteral(resourceName: "I'm With You.jpg"), "I'm With You", "Red Hot Chili Peppers", "Alternative • 2011"), 7 | (#imageLiteral(resourceName: "Making Mirrors.jpg"), "Making Mirrors", "Gotye", "Alternative • 2011"), 8 | (#imageLiteral(resourceName: "Chuck.jpg"), "Chuck", "Sum 41", "Rock • 2004"), 9 | (#imageLiteral(resourceName: "Discovery.jpg"), "Discovery", "Daft Punk", "Electronic • 2001"), 10 | (#imageLiteral(resourceName: "My Beautiful Dark Twisted Fantasy.jpg"), "My Beautiful Dark Twisted Fantasy", "Kanye West", "Hip-Hop/Rap • 2010"), 11 | (#imageLiteral(resourceName: "The Same Old Blood Rush With a New Touch.jpg"), "The Same Old Blood Rush With a New Touch", "Cute Is What We Aim For", "Alternative • 2006"), 12 | (#imageLiteral(resourceName: "Too Weird To Live, Too Rare To Die!.jpg"), "Too Weird To Live, Too Rare To Die!", "Panic! At the Disco", "Alternative • 2013"), 13 | ] 14 | 15 | let container = NSView(frame: NSRect(x: 0, y: 0, width: 800, height: albums.count / 2 * 120)) 16 | 17 | for (index, item) in albums.enumerated() { 18 | let view = ColorArtworkPreview(image: item.0, title: item.1, artist: item.2, year: item.3) 19 | view.frame.origin = NSPoint(x: index % 2 * 400, y: index / 2 * 120) 20 | container.addSubview(view) 21 | } 22 | 23 | container 24 | 25 | 26 | -------------------------------------------------------------------------------- /ColorArtwork.playground/Resources/Chuck.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddddxxx/ColorArtwork/1806d1848ac7f40cc83aa6f92d0910e345cd895e/ColorArtwork.playground/Resources/Chuck.jpg -------------------------------------------------------------------------------- /ColorArtwork.playground/Resources/Discovery.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddddxxx/ColorArtwork/1806d1848ac7f40cc83aa6f92d0910e345cd895e/ColorArtwork.playground/Resources/Discovery.jpg -------------------------------------------------------------------------------- /ColorArtwork.playground/Resources/Ghost Stories.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddddxxx/ColorArtwork/1806d1848ac7f40cc83aa6f92d0910e345cd895e/ColorArtwork.playground/Resources/Ghost Stories.jpg -------------------------------------------------------------------------------- /ColorArtwork.playground/Resources/I'm With You.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddddxxx/ColorArtwork/1806d1848ac7f40cc83aa6f92d0910e345cd895e/ColorArtwork.playground/Resources/I'm With You.jpg -------------------------------------------------------------------------------- /ColorArtwork.playground/Resources/Making Mirrors.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddddxxx/ColorArtwork/1806d1848ac7f40cc83aa6f92d0910e345cd895e/ColorArtwork.playground/Resources/Making Mirrors.jpg -------------------------------------------------------------------------------- /ColorArtwork.playground/Resources/My Beautiful Dark Twisted Fantasy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddddxxx/ColorArtwork/1806d1848ac7f40cc83aa6f92d0910e345cd895e/ColorArtwork.playground/Resources/My Beautiful Dark Twisted Fantasy.jpg -------------------------------------------------------------------------------- /ColorArtwork.playground/Resources/The Same Old Blood Rush With a New Touch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddddxxx/ColorArtwork/1806d1848ac7f40cc83aa6f92d0910e345cd895e/ColorArtwork.playground/Resources/The Same Old Blood Rush With a New Touch.jpg -------------------------------------------------------------------------------- /ColorArtwork.playground/Resources/Too Weird To Live, Too Rare To Die!.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddddxxx/ColorArtwork/1806d1848ac7f40cc83aa6f92d0910e345cd895e/ColorArtwork.playground/Resources/Too Weird To Live, Too Rare To Die!.jpg -------------------------------------------------------------------------------- /ColorArtwork.playground/Sources/ColorArtworkPreview.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import ColorArtwork 3 | 4 | public class ColorArtworkPreview: NSView { 5 | 6 | var imageView: FadeImageView 7 | var titleTextField: NSTextField 8 | var artistTextField: NSTextField 9 | var yearTextField: NSTextField 10 | 11 | public init(image: NSImage, title: String, artist: String, year: String) { 12 | let (backgroundColor, primaryColor, secondaryColor, detailColor) = image.getProminentColors() 13 | 14 | imageView = FadeImageView(frame: NSRect(x: 290, y: 10, width: 100, height: 100)) 15 | imageView.image = image 16 | imageView.backgroundColor = backgroundColor 17 | 18 | titleTextField = NSTextField(labelWithString: title) 19 | titleTextField.frame = CGRect(x: 20, y: 70, width: 250, height: 30) 20 | titleTextField.font = NSFont.systemFont(ofSize: 16) 21 | titleTextField.textColor = primaryColor 22 | 23 | artistTextField = NSTextField(labelWithString: artist) 24 | artistTextField.frame = CGRect(x: 20, y: 50, width: 250, height: 30) 25 | artistTextField.font = NSFont.systemFont(ofSize: 14) 26 | artistTextField.textColor = secondaryColor 27 | 28 | yearTextField = NSTextField(labelWithString: year) 29 | yearTextField.frame = CGRect(x: 20, y: 30, width: 250, height: 30) 30 | yearTextField.font = NSFont.systemFont(ofSize: 10) 31 | yearTextField.textColor = detailColor 32 | 33 | super.init(frame: NSRect(x: 0, y: 0, width: 400, height: 120)) 34 | 35 | wantsLayer = true 36 | layer?.backgroundColor = backgroundColor.cgColor 37 | 38 | addSubview(imageView) 39 | addSubview(titleTextField) 40 | addSubview(artistTextField) 41 | addSubview(yearTextField) 42 | } 43 | 44 | required public init?(coder: NSCoder) { 45 | fatalError("init(coder:) has not been implemented") 46 | } 47 | 48 | } 49 | 50 | class FadeImageView: NSImageView { 51 | 52 | var backgroundColor: NSColor? 53 | 54 | override func draw(_ dirtyRect: NSRect) { 55 | super.draw(dirtyRect) 56 | guard let background = backgroundColor else { 57 | return 58 | } 59 | let fadeColor = background.withAlphaComponent(0) 60 | 61 | let gradient = NSGradient(colorsAndLocations: (background, 0), (fadeColor, 0.1), (fadeColor, 0.9), (background, 1)) 62 | gradient?.draw(in: bounds, angle: 0) 63 | gradient?.draw(in: bounds, angle: 90) 64 | } 65 | 66 | } 67 | 68 | -------------------------------------------------------------------------------- /ColorArtwork.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ColorArtwork.xcodeproj/ColorArtworkTests_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | BNDL 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /ColorArtwork.xcodeproj/ColorArtwork_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.3.1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ColorArtwork.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXAggregateTarget section */ 10 | "ColorArtwork::ColorArtworkPackageTests::ProductTarget" /* ColorArtworkPackageTests */ = { 11 | isa = PBXAggregateTarget; 12 | buildConfigurationList = OBJ_45 /* Build configuration list for PBXAggregateTarget "ColorArtworkPackageTests" */; 13 | buildPhases = ( 14 | ); 15 | dependencies = ( 16 | OBJ_48 /* PBXTargetDependency */, 17 | ); 18 | name = ColorArtworkPackageTests; 19 | productName = ColorArtworkPackageTests; 20 | }; 21 | /* End PBXAggregateTarget section */ 22 | 23 | /* Begin PBXBuildFile section */ 24 | BB3F0AE91FDD0D52004A7AF3 /* Stadium Arcadium.jpg in Resources */ = {isa = PBXBuildFile; fileRef = BB3F0AE71FDD0BED004A7AF3 /* Stadium Arcadium.jpg */; }; 25 | OBJ_25 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; }; 26 | OBJ_31 /* ColorArtworkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* ColorArtworkTests.swift */; }; 27 | OBJ_33 /* ColorArtwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "ColorArtwork::ColorArtwork::Product" /* ColorArtwork.framework */; }; 28 | OBJ_40 /* ColorArtwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* ColorArtwork.swift */; }; 29 | OBJ_41 /* Image+ColorArtwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* Image+ColorArtwork.swift */; }; 30 | OBJ_42 /* RGBColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* RGBColor.swift */; }; 31 | /* End PBXBuildFile section */ 32 | 33 | /* Begin PBXContainerItemProxy section */ 34 | BB3F0AE51FDD0A70004A7AF3 /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = OBJ_1 /* Project object */; 37 | proxyType = 1; 38 | remoteGlobalIDString = "ColorArtwork::ColorArtwork"; 39 | remoteInfo = ColorArtwork; 40 | }; 41 | BB3F0AE61FDD0A70004A7AF3 /* PBXContainerItemProxy */ = { 42 | isa = PBXContainerItemProxy; 43 | containerPortal = OBJ_1 /* Project object */; 44 | proxyType = 1; 45 | remoteGlobalIDString = "ColorArtwork::ColorArtworkTests"; 46 | remoteInfo = ColorArtworkTests; 47 | }; 48 | /* End PBXContainerItemProxy section */ 49 | 50 | /* Begin PBXFileReference section */ 51 | BB3F0AE71FDD0BED004A7AF3 /* Stadium Arcadium.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "Stadium Arcadium.jpg"; sourceTree = ""; }; 52 | "ColorArtwork::ColorArtwork::Product" /* ColorArtwork.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ColorArtwork.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | "ColorArtwork::ColorArtworkTests::Product" /* ColorArtworkTests.xctest */ = {isa = PBXFileReference; lastKnownFileType = file; path = ColorArtworkTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | OBJ_10 /* Image+ColorArtwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Image+ColorArtwork.swift"; sourceTree = ""; }; 55 | OBJ_11 /* RGBColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RGBColor.swift; sourceTree = ""; }; 56 | OBJ_14 /* ColorArtworkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorArtworkTests.swift; sourceTree = ""; }; 57 | OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 58 | OBJ_9 /* ColorArtwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorArtwork.swift; sourceTree = ""; }; 59 | /* End PBXFileReference section */ 60 | 61 | /* Begin PBXFrameworksBuildPhase section */ 62 | OBJ_32 /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 0; 65 | files = ( 66 | OBJ_33 /* ColorArtwork.framework in Frameworks */, 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | OBJ_43 /* Frameworks */ = { 71 | isa = PBXFrameworksBuildPhase; 72 | buildActionMask = 0; 73 | files = ( 74 | ); 75 | runOnlyForDeploymentPostprocessing = 0; 76 | }; 77 | /* End PBXFrameworksBuildPhase section */ 78 | 79 | /* Begin PBXGroup section */ 80 | OBJ_12 /* Tests */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | OBJ_13 /* ColorArtworkTests */, 84 | ); 85 | name = Tests; 86 | sourceTree = SOURCE_ROOT; 87 | }; 88 | OBJ_13 /* ColorArtworkTests */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | OBJ_14 /* ColorArtworkTests.swift */, 92 | BB3F0AE71FDD0BED004A7AF3 /* Stadium Arcadium.jpg */, 93 | ); 94 | name = ColorArtworkTests; 95 | path = Tests/ColorArtworkTests; 96 | sourceTree = SOURCE_ROOT; 97 | }; 98 | OBJ_17 /* Products */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | "ColorArtwork::ColorArtworkTests::Product" /* ColorArtworkTests.xctest */, 102 | "ColorArtwork::ColorArtwork::Product" /* ColorArtwork.framework */, 103 | ); 104 | name = Products; 105 | sourceTree = BUILT_PRODUCTS_DIR; 106 | }; 107 | OBJ_5 = { 108 | isa = PBXGroup; 109 | children = ( 110 | OBJ_6 /* Package.swift */, 111 | OBJ_7 /* Sources */, 112 | OBJ_12 /* Tests */, 113 | OBJ_17 /* Products */, 114 | ); 115 | sourceTree = ""; 116 | }; 117 | OBJ_7 /* Sources */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | OBJ_8 /* ColorArtwork */, 121 | ); 122 | name = Sources; 123 | sourceTree = SOURCE_ROOT; 124 | }; 125 | OBJ_8 /* ColorArtwork */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | OBJ_9 /* ColorArtwork.swift */, 129 | OBJ_10 /* Image+ColorArtwork.swift */, 130 | OBJ_11 /* RGBColor.swift */, 131 | ); 132 | name = ColorArtwork; 133 | path = Sources/ColorArtwork; 134 | sourceTree = SOURCE_ROOT; 135 | }; 136 | /* End PBXGroup section */ 137 | 138 | /* Begin PBXNativeTarget section */ 139 | "ColorArtwork::ColorArtwork" /* ColorArtwork */ = { 140 | isa = PBXNativeTarget; 141 | buildConfigurationList = OBJ_36 /* Build configuration list for PBXNativeTarget "ColorArtwork" */; 142 | buildPhases = ( 143 | OBJ_39 /* Sources */, 144 | OBJ_43 /* Frameworks */, 145 | ); 146 | buildRules = ( 147 | ); 148 | dependencies = ( 149 | ); 150 | name = ColorArtwork; 151 | productName = ColorArtwork; 152 | productReference = "ColorArtwork::ColorArtwork::Product" /* ColorArtwork.framework */; 153 | productType = "com.apple.product-type.framework"; 154 | }; 155 | "ColorArtwork::ColorArtworkTests" /* ColorArtworkTests */ = { 156 | isa = PBXNativeTarget; 157 | buildConfigurationList = OBJ_27 /* Build configuration list for PBXNativeTarget "ColorArtworkTests" */; 158 | buildPhases = ( 159 | OBJ_30 /* Sources */, 160 | BB3F0AE81FDD0D49004A7AF3 /* Resources */, 161 | OBJ_32 /* Frameworks */, 162 | ); 163 | buildRules = ( 164 | ); 165 | dependencies = ( 166 | OBJ_34 /* PBXTargetDependency */, 167 | ); 168 | name = ColorArtworkTests; 169 | productName = ColorArtworkTests; 170 | productReference = "ColorArtwork::ColorArtworkTests::Product" /* ColorArtworkTests.xctest */; 171 | productType = "com.apple.product-type.bundle.unit-test"; 172 | }; 173 | "ColorArtwork::SwiftPMPackageDescription" /* ColorArtworkPackageDescription */ = { 174 | isa = PBXNativeTarget; 175 | buildConfigurationList = OBJ_21 /* Build configuration list for PBXNativeTarget "ColorArtworkPackageDescription" */; 176 | buildPhases = ( 177 | OBJ_24 /* Sources */, 178 | ); 179 | buildRules = ( 180 | ); 181 | dependencies = ( 182 | ); 183 | name = ColorArtworkPackageDescription; 184 | productName = ColorArtworkPackageDescription; 185 | productType = "com.apple.product-type.framework"; 186 | }; 187 | /* End PBXNativeTarget section */ 188 | 189 | /* Begin PBXProject section */ 190 | OBJ_1 /* Project object */ = { 191 | isa = PBXProject; 192 | attributes = { 193 | LastUpgradeCheck = 9999; 194 | }; 195 | buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "ColorArtwork" */; 196 | compatibilityVersion = "Xcode 3.2"; 197 | developmentRegion = English; 198 | hasScannedForEncodings = 0; 199 | knownRegions = ( 200 | en, 201 | ); 202 | mainGroup = OBJ_5; 203 | productRefGroup = OBJ_17 /* Products */; 204 | projectDirPath = ""; 205 | projectRoot = ""; 206 | targets = ( 207 | "ColorArtwork::SwiftPMPackageDescription" /* ColorArtworkPackageDescription */, 208 | "ColorArtwork::ColorArtworkTests" /* ColorArtworkTests */, 209 | "ColorArtwork::ColorArtwork" /* ColorArtwork */, 210 | "ColorArtwork::ColorArtworkPackageTests::ProductTarget" /* ColorArtworkPackageTests */, 211 | ); 212 | }; 213 | /* End PBXProject section */ 214 | 215 | /* Begin PBXResourcesBuildPhase section */ 216 | BB3F0AE81FDD0D49004A7AF3 /* Resources */ = { 217 | isa = PBXResourcesBuildPhase; 218 | buildActionMask = 2147483647; 219 | files = ( 220 | BB3F0AE91FDD0D52004A7AF3 /* Stadium Arcadium.jpg in Resources */, 221 | ); 222 | runOnlyForDeploymentPostprocessing = 0; 223 | }; 224 | /* End PBXResourcesBuildPhase section */ 225 | 226 | /* Begin PBXSourcesBuildPhase section */ 227 | OBJ_24 /* Sources */ = { 228 | isa = PBXSourcesBuildPhase; 229 | buildActionMask = 0; 230 | files = ( 231 | OBJ_25 /* Package.swift in Sources */, 232 | ); 233 | runOnlyForDeploymentPostprocessing = 0; 234 | }; 235 | OBJ_30 /* Sources */ = { 236 | isa = PBXSourcesBuildPhase; 237 | buildActionMask = 0; 238 | files = ( 239 | OBJ_31 /* ColorArtworkTests.swift in Sources */, 240 | ); 241 | runOnlyForDeploymentPostprocessing = 0; 242 | }; 243 | OBJ_39 /* Sources */ = { 244 | isa = PBXSourcesBuildPhase; 245 | buildActionMask = 0; 246 | files = ( 247 | OBJ_40 /* ColorArtwork.swift in Sources */, 248 | OBJ_41 /* Image+ColorArtwork.swift in Sources */, 249 | OBJ_42 /* RGBColor.swift in Sources */, 250 | ); 251 | runOnlyForDeploymentPostprocessing = 0; 252 | }; 253 | /* End PBXSourcesBuildPhase section */ 254 | 255 | /* Begin PBXTargetDependency section */ 256 | OBJ_34 /* PBXTargetDependency */ = { 257 | isa = PBXTargetDependency; 258 | target = "ColorArtwork::ColorArtwork" /* ColorArtwork */; 259 | targetProxy = BB3F0AE51FDD0A70004A7AF3 /* PBXContainerItemProxy */; 260 | }; 261 | OBJ_48 /* PBXTargetDependency */ = { 262 | isa = PBXTargetDependency; 263 | target = "ColorArtwork::ColorArtworkTests" /* ColorArtworkTests */; 264 | targetProxy = BB3F0AE61FDD0A70004A7AF3 /* PBXContainerItemProxy */; 265 | }; 266 | /* End PBXTargetDependency section */ 267 | 268 | /* Begin XCBuildConfiguration section */ 269 | OBJ_22 /* Debug */ = { 270 | isa = XCBuildConfiguration; 271 | buildSettings = { 272 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 273 | LD = /usr/bin/true; 274 | MACOSX_DEPLOYMENT_TARGET = 10.10; 275 | OTHER_SWIFT_FLAGS = "-swift-version 4 -I /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/pm/4 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk"; 276 | SWIFT_VERSION = 4.0; 277 | TVOS_DEPLOYMENT_TARGET = 9.0; 278 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 279 | }; 280 | name = Debug; 281 | }; 282 | OBJ_23 /* Release */ = { 283 | isa = XCBuildConfiguration; 284 | buildSettings = { 285 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 286 | LD = /usr/bin/true; 287 | MACOSX_DEPLOYMENT_TARGET = 10.10; 288 | OTHER_SWIFT_FLAGS = "-swift-version 4 -I /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/pm/4 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk"; 289 | SWIFT_VERSION = 4.0; 290 | TVOS_DEPLOYMENT_TARGET = 9.0; 291 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 292 | }; 293 | name = Release; 294 | }; 295 | OBJ_28 /* Debug */ = { 296 | isa = XCBuildConfiguration; 297 | buildSettings = { 298 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 299 | FRAMEWORK_SEARCH_PATHS = ( 300 | "$(inherited)", 301 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 302 | ); 303 | HEADER_SEARCH_PATHS = "$(inherited)"; 304 | INFOPLIST_FILE = ColorArtwork.xcodeproj/ColorArtworkTests_Info.plist; 305 | LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks @loader_path/Frameworks"; 306 | OTHER_LDFLAGS = "$(inherited)"; 307 | OTHER_SWIFT_FLAGS = "$(inherited)"; 308 | SWIFT_VERSION = 4.0; 309 | TARGET_NAME = ColorArtworkTests; 310 | }; 311 | name = Debug; 312 | }; 313 | OBJ_29 /* Release */ = { 314 | isa = XCBuildConfiguration; 315 | buildSettings = { 316 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 317 | FRAMEWORK_SEARCH_PATHS = ( 318 | "$(inherited)", 319 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 320 | ); 321 | HEADER_SEARCH_PATHS = "$(inherited)"; 322 | INFOPLIST_FILE = ColorArtwork.xcodeproj/ColorArtworkTests_Info.plist; 323 | LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks @loader_path/Frameworks"; 324 | OTHER_LDFLAGS = "$(inherited)"; 325 | OTHER_SWIFT_FLAGS = "$(inherited)"; 326 | SWIFT_VERSION = 4.0; 327 | TARGET_NAME = ColorArtworkTests; 328 | }; 329 | name = Release; 330 | }; 331 | OBJ_3 /* Debug */ = { 332 | isa = XCBuildConfiguration; 333 | buildSettings = { 334 | CLANG_ENABLE_OBJC_ARC = YES; 335 | COMBINE_HIDPI_IMAGES = YES; 336 | COPY_PHASE_STRIP = NO; 337 | DEBUG_INFORMATION_FORMAT = dwarf; 338 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 339 | ENABLE_NS_ASSERTIONS = YES; 340 | GCC_OPTIMIZATION_LEVEL = 0; 341 | MACOSX_DEPLOYMENT_TARGET = 10.10; 342 | ONLY_ACTIVE_ARCH = YES; 343 | OTHER_SWIFT_FLAGS = "-DXcode"; 344 | PRODUCT_NAME = "$(TARGET_NAME)"; 345 | SDKROOT = macosx; 346 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; 347 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE; 348 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 349 | USE_HEADERMAP = NO; 350 | }; 351 | name = Debug; 352 | }; 353 | OBJ_37 /* Debug */ = { 354 | isa = XCBuildConfiguration; 355 | buildSettings = { 356 | ENABLE_TESTABILITY = YES; 357 | FRAMEWORK_SEARCH_PATHS = ( 358 | "$(inherited)", 359 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 360 | ); 361 | HEADER_SEARCH_PATHS = "$(inherited)"; 362 | INFOPLIST_FILE = ColorArtwork.xcodeproj/ColorArtwork_Info.plist; 363 | LD_RUNPATH_SEARCH_PATHS = "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; 364 | OTHER_LDFLAGS = "$(inherited)"; 365 | OTHER_SWIFT_FLAGS = "$(inherited)"; 366 | PRODUCT_BUNDLE_IDENTIFIER = ColorArtwork; 367 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 368 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 369 | SKIP_INSTALL = YES; 370 | SWIFT_VERSION = 4.0; 371 | TARGET_NAME = ColorArtwork; 372 | }; 373 | name = Debug; 374 | }; 375 | OBJ_38 /* Release */ = { 376 | isa = XCBuildConfiguration; 377 | buildSettings = { 378 | ENABLE_TESTABILITY = YES; 379 | FRAMEWORK_SEARCH_PATHS = ( 380 | "$(inherited)", 381 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 382 | ); 383 | HEADER_SEARCH_PATHS = "$(inherited)"; 384 | INFOPLIST_FILE = ColorArtwork.xcodeproj/ColorArtwork_Info.plist; 385 | LD_RUNPATH_SEARCH_PATHS = "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; 386 | OTHER_LDFLAGS = "$(inherited)"; 387 | OTHER_SWIFT_FLAGS = "$(inherited)"; 388 | PRODUCT_BUNDLE_IDENTIFIER = ColorArtwork; 389 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 390 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 391 | SKIP_INSTALL = YES; 392 | SWIFT_VERSION = 4.0; 393 | TARGET_NAME = ColorArtwork; 394 | }; 395 | name = Release; 396 | }; 397 | OBJ_4 /* Release */ = { 398 | isa = XCBuildConfiguration; 399 | buildSettings = { 400 | CLANG_ENABLE_OBJC_ARC = YES; 401 | COMBINE_HIDPI_IMAGES = YES; 402 | COPY_PHASE_STRIP = YES; 403 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 404 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 405 | GCC_OPTIMIZATION_LEVEL = s; 406 | MACOSX_DEPLOYMENT_TARGET = 10.10; 407 | OTHER_SWIFT_FLAGS = "-DXcode"; 408 | PRODUCT_NAME = "$(TARGET_NAME)"; 409 | SDKROOT = macosx; 410 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; 411 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE; 412 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 413 | USE_HEADERMAP = NO; 414 | }; 415 | name = Release; 416 | }; 417 | OBJ_46 /* Debug */ = { 418 | isa = XCBuildConfiguration; 419 | buildSettings = { 420 | }; 421 | name = Debug; 422 | }; 423 | OBJ_47 /* Release */ = { 424 | isa = XCBuildConfiguration; 425 | buildSettings = { 426 | }; 427 | name = Release; 428 | }; 429 | /* End XCBuildConfiguration section */ 430 | 431 | /* Begin XCConfigurationList section */ 432 | OBJ_2 /* Build configuration list for PBXProject "ColorArtwork" */ = { 433 | isa = XCConfigurationList; 434 | buildConfigurations = ( 435 | OBJ_3 /* Debug */, 436 | OBJ_4 /* Release */, 437 | ); 438 | defaultConfigurationIsVisible = 0; 439 | defaultConfigurationName = Debug; 440 | }; 441 | OBJ_21 /* Build configuration list for PBXNativeTarget "ColorArtworkPackageDescription" */ = { 442 | isa = XCConfigurationList; 443 | buildConfigurations = ( 444 | OBJ_22 /* Debug */, 445 | OBJ_23 /* Release */, 446 | ); 447 | defaultConfigurationIsVisible = 0; 448 | defaultConfigurationName = Debug; 449 | }; 450 | OBJ_27 /* Build configuration list for PBXNativeTarget "ColorArtworkTests" */ = { 451 | isa = XCConfigurationList; 452 | buildConfigurations = ( 453 | OBJ_28 /* Debug */, 454 | OBJ_29 /* Release */, 455 | ); 456 | defaultConfigurationIsVisible = 0; 457 | defaultConfigurationName = Debug; 458 | }; 459 | OBJ_36 /* Build configuration list for PBXNativeTarget "ColorArtwork" */ = { 460 | isa = XCConfigurationList; 461 | buildConfigurations = ( 462 | OBJ_37 /* Debug */, 463 | OBJ_38 /* Release */, 464 | ); 465 | defaultConfigurationIsVisible = 0; 466 | defaultConfigurationName = Debug; 467 | }; 468 | OBJ_45 /* Build configuration list for PBXAggregateTarget "ColorArtworkPackageTests" */ = { 469 | isa = XCConfigurationList; 470 | buildConfigurations = ( 471 | OBJ_46 /* Debug */, 472 | OBJ_47 /* Release */, 473 | ); 474 | defaultConfigurationIsVisible = 0; 475 | defaultConfigurationName = Debug; 476 | }; 477 | /* End XCConfigurationList section */ 478 | }; 479 | rootObject = OBJ_1 /* Project object */; 480 | } 481 | -------------------------------------------------------------------------------- /ColorArtwork.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ColorArtwork.xcodeproj/xcshareddata/xcschemes/ColorArtwork-Package.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 76 | 78 | 79 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /ColorArtwork.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 DengXiang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "ColorArtwork", 8 | products: [ 9 | .library( 10 | name: "ColorArtwork", 11 | targets: ["ColorArtwork"]), 12 | ], 13 | targets: [ 14 | .target( 15 | name: "ColorArtwork"), 16 | .testTarget( 17 | name: "ColorArtworkTests", 18 | dependencies: ["ColorArtwork"]) 19 | ] 20 | ) 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ColorArtwork 2 | 3 | [![Build Status](https://travis-ci.org/ddddxxx/ColorArtwork.svg?branch=master)](https://travis-ci.org/ddddxxx/ColorArtwork) 4 | ![supports](https://img.shields.io/badge/supports-Carthage%20%7C%20Swift_Package_Manager-brightgreen.svg) 5 | ![platforms](https://img.shields.io/badge/platforms-macOS%20%7C%20iOS%20%7C%20tvOS%20%7C%20watchOS-lightgrey.svg) 6 | [![License](https://img.shields.io/github/license/ddddxxx/ColorArtwork.svg)](LICENSE) 7 | [![codebeat badge](https://codebeat.co/badges/3a45abf9-c765-49a4-b060-bf774d1288b6)](https://codebeat.co/projects/github-com-xqs6lb3a-colorartwork-master) 8 | 9 | Swift-based iTunes 11-style color matching code. Inspired by [Panic Blog](https://panic.com/blog/itunes-11-and-colors/). 10 | 11 | ![preview](docs/img/preview.png) 12 | 13 | ## Requirements 14 | 15 | - macOS 10.10+ / iOS 9.0+ / tvOS 9.0+ / watchOS 2.0+ 16 | - Xcode 8+ 17 | - Swift 3.0+ 18 | 19 | ## Installation 20 | 21 | ### [Carthage](https://github.com/Carthage/Carthage) 22 | 23 | Add the project to your `Cartfile`: 24 | 25 | ``` 26 | github "ddddxxx/ColorArtwork" 27 | ``` 28 | 29 | ### [Swift Package Manager](https://github.com/apple/swift-package-manager) 30 | 31 | Add the project to your `Package.swift` file: 32 | 33 | ```swift 34 | let package = Package( 35 | dependencies: [ 36 | .Package(url: "https://github.com/ddddxxx/ColorArtwork", majorVersion: 0) 37 | ] 38 | ) 39 | ``` 40 | 41 | ## Usage 42 | 43 | ```swift 44 | import ColorArtwork 45 | ``` 46 | 47 | ```swift 48 | // IMAGE: UIImage / NSImage / CGImage 49 | // UIImage -> UIColor 50 | // NSImage -> NSColor 51 | // CGImage -> CGColor 52 | // SIZE: scale down size before analyzing 53 | // nil(default): auto scale 54 | // zero: do not scale 55 | let (backgroundColor, primaryColor, secondaryColor, detailColor) = IMAGE.getProminentColors(scale: SIZE) 56 | ``` 57 | 58 | ## License 59 | 60 | ColorArtwork is available under the MIT license. See the [LICENSE file](LICENSE). 61 | 62 | -------------------------------------------------------------------------------- /Sources/ColorArtwork/ColorArtwork.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorArtwork.swift 3 | // 4 | // This file is part of ColorArtwork. 5 | // Copyright (c) 2017 Xander Deng 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | 18 | import Foundation 19 | import CoreGraphics 20 | 21 | class ColorArtwork { 22 | 23 | var image: CGImage 24 | 25 | var backgroundColor: CGColor? 26 | var primaryColor: CGColor? 27 | var secondaryColor: CGColor? 28 | var detailColor: CGColor? 29 | 30 | static let defaultScaleSize = CGSize(width: 300, height: 300) 31 | 32 | init(image: CGImage, scale: CGSize?) { 33 | let scale = scale ?? ColorArtwork.defaultScaleSize 34 | 35 | // never scale up 36 | if image.width < Int(scale.width) || image.height < Int(scale.height) { 37 | self.image = image 38 | return 39 | } 40 | 41 | // do not scale to zero / current size 42 | if scale == .zero || scale == CGSize(width: image.width, height: image.height) { 43 | self.image = image 44 | return 45 | } 46 | 47 | self.image = image.scaling(to: scale) ?? image 48 | } 49 | 50 | func analyze() { 51 | guard let data = image.dataProvider?.data, 52 | let dataPtr = CFDataGetBytePtr(data) else { 53 | return 54 | } 55 | 56 | let edgeColors = findEdgeColors(data: dataPtr) 57 | let background = findEdgeColor(in: edgeColors) ?? RGBColor(r: 1, g: 1, b: 1) 58 | 59 | let colors = findColors(data: dataPtr, isDarkBackground: background.isDark) 60 | let textColors = findTextColor(in: colors, background: background) 61 | 62 | let fallback: CGColor = background.isDark ? .white : .black 63 | 64 | backgroundColor = background.cgColor 65 | primaryColor = textColors.primary?.cgColor ?? fallback 66 | secondaryColor = textColors.secondary?.cgColor ?? fallback 67 | detailColor = textColors.detail?.cgColor ?? fallback 68 | } 69 | 70 | func findEdgeColors(data: UnsafePointer) -> [CountedRGBColor] { 71 | let width = image.width 72 | let height = image.height 73 | 74 | let bpr = image.bytesPerRow 75 | let bpp = image.bitsPerPixel 76 | let bpc = image.bitsPerComponent 77 | let bytesPerPixel = bpp / bpc 78 | 79 | let edgeColorSet = NSCountedSet() 80 | 81 | for row in 0 ..< height { 82 | let leftEdgeIndex = row * bpr 83 | let leftEdgeColor = RGBColor(compnents: data + leftEdgeIndex) 84 | edgeColorSet.add(leftEdgeColor) 85 | 86 | let rightEdgeIndex = row * bpr + (width-1) * bytesPerPixel 87 | let rightEdgeColor = RGBColor(compnents: data + rightEdgeIndex) 88 | edgeColorSet.add(rightEdgeColor) 89 | } 90 | for col in 0 ..< width { 91 | let topEdgeIndex = col * bytesPerPixel 92 | let topEdgeColor = RGBColor(compnents: data + topEdgeIndex) 93 | edgeColorSet.add(topEdgeColor) 94 | 95 | let bottomEdgeIndex = (height-1) * bpr + col * bytesPerPixel 96 | let bottomEdgeColor = RGBColor(compnents: data + bottomEdgeIndex) 97 | edgeColorSet.add(bottomEdgeColor) 98 | } 99 | 100 | let edgeColors = edgeColorSet.objectEnumerator().allObjects.map() { 101 | CountedRGBColor(color: ($0 as! RGBColor), count: edgeColorSet.count(for: $0)) 102 | } 103 | 104 | return edgeColors 105 | } 106 | 107 | func findColors(data: UnsafePointer, isDarkBackground: Bool) -> [CountedRGBColor] { 108 | let width = image.width 109 | let height = image.height 110 | 111 | let bpr = image.bytesPerRow 112 | let bpp = image.bitsPerPixel 113 | let bpc = image.bitsPerComponent 114 | let bytesPerPixel = bpp / bpc 115 | 116 | let colorSet = NSCountedSet() 117 | 118 | for row in 0 ..< height { 119 | for col in 0 ..< width { 120 | let index = row * bpr + col * bytesPerPixel 121 | let color = RGBColor(compnents: data + index) 122 | if color.isDark != isDarkBackground { 123 | colorSet.add(color) 124 | } 125 | } 126 | } 127 | 128 | let colors = colorSet.objectEnumerator().allObjects.flatMap() { color -> CountedRGBColor? in 129 | let color = color as! RGBColor 130 | let count = colorSet.count(for: color) 131 | if count > 2 { 132 | return CountedRGBColor(color: color, count: count) 133 | } else { 134 | return nil 135 | } 136 | } 137 | 138 | return colors 139 | } 140 | 141 | func findEdgeColor(in edgeColors: [CountedRGBColor]) -> RGBColor? { 142 | let threshold = edgeColors.count / 100 143 | let edgeColors = edgeColors.filter(){ 144 | $0.count > threshold 145 | }.sorted() { 146 | $0.count > $1.count 147 | } 148 | 149 | guard edgeColors.count > 0 else { 150 | return nil 151 | } 152 | 153 | var proposed = edgeColors.first! 154 | if proposed.color.isBlackOrWhite { 155 | for edgeColor in edgeColors.dropFirst() { 156 | guard edgeColor.count > proposed.count / 3 else { 157 | break 158 | } 159 | if !edgeColor.color.isBlackOrWhite { 160 | proposed = edgeColor 161 | break 162 | } 163 | } 164 | } 165 | 166 | return proposed.color 167 | } 168 | 169 | func findTextColor(in colors: [CountedRGBColor], background: RGBColor) -> (primary: RGBColor?, secondary: RGBColor?, detail: RGBColor?) { 170 | let colors = colors.filter(){ 171 | $0.color.isContrastable(with: background) 172 | }.sorted() { 173 | $0.count > $1.count 174 | } 175 | 176 | var primary: RGBColor? = nil 177 | var secondary: RGBColor? = nil 178 | var detail: RGBColor? = nil 179 | 180 | for countedColor in colors { 181 | let color = countedColor.color.withMinimumSaturation(0.15) 182 | guard let c1 = primary else { 183 | primary = color 184 | continue 185 | } 186 | guard let c2 = secondary else { 187 | if c1.isDistinct(with: color) { 188 | secondary = color 189 | } 190 | continue 191 | } 192 | if detail == nil { 193 | if c1.isDistinct(with: color) && c2.isDistinct(with: color) { 194 | detail = color 195 | } 196 | continue 197 | } 198 | break 199 | } 200 | 201 | return (primary, secondary, detail) 202 | } 203 | 204 | } 205 | 206 | extension CGImage { 207 | 208 | func scaling(to size: CGSize) -> CGImage? { 209 | let context = CGContext(data: nil, 210 | width: Int(size.width), 211 | height: Int(size.height), 212 | bitsPerComponent: bitsPerComponent, 213 | bytesPerRow: bytesPerRow, 214 | space: CGColorSpace(name: CGColorSpace.sRGB)!, 215 | bitmapInfo: bitmapInfo.rawValue) 216 | context?.draw(self, in: CGRect(origin: .zero, size: size)) 217 | 218 | return context?.makeImage() 219 | } 220 | 221 | } 222 | 223 | extension CGColor { 224 | 225 | static let white = CGColor(colorSpace: CGColorSpace(name: CGColorSpace.sRGB)!, components: [1, 1, 1, 1])! 226 | 227 | static let black = CGColor(colorSpace: CGColorSpace(name: CGColorSpace.sRGB)!, components: [0, 0, 0, 1])! 228 | 229 | } 230 | 231 | -------------------------------------------------------------------------------- /Sources/ColorArtwork/Image+ColorArtwork.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConvenienceExtension.swift 3 | // 4 | // This file is part of ColorArtwork. 5 | // Copyright (c) 2017 Xander Deng 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | 18 | import CoreGraphics 19 | 20 | extension CGImage { 21 | 22 | public func getProminentColors(scale size: CGSize? = nil) -> (backgroundColor: CGColor, primaryColor: CGColor, secondaryColor: CGColor, detailColor: CGColor) { 23 | let colorArtwork = ColorArtwork(image: self, scale: size) 24 | colorArtwork.analyze() 25 | guard let backgroundColor = colorArtwork.backgroundColor, 26 | let primaryColor = colorArtwork.primaryColor, 27 | let secondaryColor = colorArtwork.secondaryColor, 28 | let detailColor = colorArtwork.detailColor else { 29 | return (.white, .black, .black, .black) 30 | } 31 | 32 | return (backgroundColor, primaryColor, secondaryColor, detailColor) 33 | } 34 | 35 | } 36 | 37 | #if os(macOS) 38 | 39 | import Cocoa 40 | 41 | extension NSImage { 42 | 43 | public func getProminentColors(scale size: NSSize? = nil) -> (backgroundColor: NSColor, primaryColor: NSColor, secondaryColor: NSColor, detailColor: NSColor) { 44 | let (backgroundColor, primaryColor, secondaryColor, detailColor) = cgImage(forProposedRect: nil, context: nil, hints: nil)!.getProminentColors(scale: size) 45 | 46 | return (NSColor(cgColor: backgroundColor)!, NSColor(cgColor: primaryColor)!, NSColor(cgColor: secondaryColor)!, NSColor(cgColor: detailColor)!) 47 | } 48 | 49 | } 50 | 51 | #elseif os(iOS) || os(tvOS) || os(watchOS) 52 | 53 | import UIKit 54 | 55 | extension UIImage { 56 | 57 | public func getProminentColors(scale size: CGSize? = nil) -> (backgroundColor: UIColor, primaryColor: UIColor, secondaryColor: UIColor, detailColor: UIColor) { 58 | let (backgroundColor, primaryColor, secondaryColor, detailColor) = cgImage!.getProminentColors(scale: size) 59 | 60 | return (UIColor(cgColor: backgroundColor), UIColor(cgColor: primaryColor), UIColor(cgColor: secondaryColor), UIColor(cgColor: detailColor)) 61 | } 62 | 63 | } 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /Sources/ColorArtwork/RGBColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RGBColor.swift 3 | // 4 | // This file is part of ColorArtwork. 5 | // Copyright (c) 2017 Xander Deng 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | 18 | import Foundation 19 | import CoreGraphics 20 | 21 | struct RGBColor { 22 | 23 | let r: CGFloat 24 | let g: CGFloat 25 | let b: CGFloat 26 | 27 | init(r:CGFloat, g: CGFloat, b: CGFloat) { 28 | self.r = r 29 | self.g = g 30 | self.b = b 31 | } 32 | } 33 | 34 | extension RGBColor { 35 | 36 | init(compnents: UnsafePointer) { 37 | self.r = CGFloat(compnents[0]) / 255 38 | self.g = CGFloat(compnents[1]) / 255 39 | self.b = CGFloat(compnents[2]) / 255 40 | } 41 | 42 | var luma: CGFloat { 43 | return 0.2126 * r + 0.7152 * g + 0.0722 * b 44 | } 45 | 46 | var isDark: Bool { 47 | return luma < 0.5 48 | } 49 | 50 | var isBlackOrWhite: Bool { 51 | if r > 0.9 && g > 0.9 && b > 0.9 { 52 | return true 53 | } 54 | if r < 0.15 && g < 0.15 && b < 0.15 { 55 | return true 56 | } 57 | return false 58 | } 59 | 60 | var cgColor: CGColor { 61 | return CGColor(colorSpace: CGColorSpace(name: CGColorSpace.sRGB)!, components: [r, g, b, 1.0])! 62 | } 63 | 64 | func isContrastable(with c:RGBColor) -> Bool { 65 | return max(luma, c.luma) / min(luma, c.luma) > 1.6 66 | } 67 | 68 | func isDistinct(with c:RGBColor) -> Bool { 69 | let threshold: CGFloat = 0.25 70 | if abs(r-c.r) > threshold || abs(g-c.g) > threshold || abs(b-c.b) > threshold, 71 | (abs(r-g) > 0.1 && abs(r-b) > 0.1) || (abs(c.r-c.g) > 0.1 && abs(c.r-c.b) > 0.1) { 72 | return true 73 | } 74 | return false 75 | } 76 | 77 | } 78 | 79 | extension RGBColor { 80 | 81 | var h: CGFloat { 82 | let maxComponent = max(r, g, b) 83 | let minComponent = min(r, g, b) 84 | let delta = maxComponent - minComponent 85 | 86 | guard maxComponent != 0 else { 87 | return 0 // undefined 88 | } 89 | 90 | if delta < 0.00001 { 91 | return 0 92 | } 93 | 94 | var out: CGFloat 95 | if r == maxComponent { 96 | out = (g - b) / delta 97 | } else if g == maxComponent { 98 | out = (b - r) / delta + 2.0 99 | } else { 100 | out = (r - g) / 4.0 101 | } 102 | 103 | out = abs(out / 6) 104 | 105 | return out 106 | } 107 | 108 | var s: CGFloat { 109 | let maxComponent = max(r, g, b) 110 | let minComponent = min(r, g, b) 111 | let delta = maxComponent - minComponent 112 | 113 | if maxComponent == 0 { 114 | return 0 115 | } else { 116 | return delta / maxComponent 117 | } 118 | } 119 | 120 | var v: CGFloat { 121 | return max(r, g, b) 122 | } 123 | 124 | init(h: CGFloat, s: CGFloat, v: CGFloat) { 125 | if v <= 0 { 126 | self.init(r:0, g:0, b:0) 127 | return 128 | } else if s <= 0 { 129 | self.init(r:v, g:v, b:v) 130 | return 131 | } 132 | 133 | let hf = h * 60 134 | let i = Int(hf) 135 | let f = hf - CGFloat(i) 136 | let pv = v * (1 - s) 137 | let qv = v * (1 - s * f) 138 | let tv = v * (1 - s * (1-f)) 139 | 140 | switch i { 141 | case 0: 142 | self.init(r:v, g:tv, b:pv) 143 | case 1: 144 | self.init(r:qv, g:v, b:pv) 145 | case 2: 146 | self.init(r:pv, g:v, b:tv) 147 | case 3: 148 | self.init(r:pv, g:qv, b:v) 149 | case 4: 150 | self.init(r:tv, g:pv, b:v) 151 | case 5: 152 | self.init(r:v, g:pv, b:qv) 153 | case 6: 154 | self.init(r:v, g:tv, b:pv) 155 | case -1: 156 | self.init(r:v, g:pv, b:qv) 157 | default: 158 | self.init(r:v, g:v, b:v) 159 | } 160 | } 161 | 162 | func withMinimumSaturation(_ saturation: CGFloat) -> RGBColor { 163 | if s < saturation { 164 | return RGBColor(h: h, s: saturation, v: v) 165 | } else { 166 | return self 167 | } 168 | } 169 | } 170 | 171 | extension RGBColor: Hashable { 172 | 173 | var hashValue: Int { 174 | return r.hashValue &+ g.hashValue &+ b.hashValue 175 | } 176 | 177 | public static func ==(lhs: RGBColor, rhs: RGBColor) -> Bool { 178 | return lhs.hashValue == rhs.hashValue 179 | } 180 | } 181 | 182 | 183 | struct CountedRGBColor { 184 | 185 | let color: RGBColor 186 | let count: Int 187 | } 188 | 189 | -------------------------------------------------------------------------------- /Tests/ColorArtworkTests/ColorArtworkTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorArtworkTests.swift 3 | // 4 | // This file is part of ColorArtwork. 5 | // Copyright (c) 2017 Xander Deng 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | 18 | import Cocoa 19 | import XCTest 20 | @testable import ColorArtwork 21 | 22 | class ColorArtworkTests: XCTestCase { 23 | 24 | func testExtensionAnalyzing() { 25 | let bundle = Bundle(for: type(of: self)) 26 | let testImage = bundle.image(forResource: .init("Stadium Arcadium"))?.cgImage(forProposedRect: nil, context: nil, hints: nil) 27 | measure { 28 | let colors = testImage!.getProminentColors() 29 | XCTAssertNotNil(colors) 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Tests/ColorArtworkTests/Stadium Arcadium.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddddxxx/ColorArtwork/1806d1848ac7f40cc83aa6f92d0910e345cd895e/Tests/ColorArtworkTests/Stadium Arcadium.jpg -------------------------------------------------------------------------------- /docs/img/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddddxxx/ColorArtwork/1806d1848ac7f40cc83aa6f92d0910e345cd895e/docs/img/preview.png --------------------------------------------------------------------------------