├── .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 | [](https://travis-ci.org/ddddxxx/ColorArtwork)
4 | 
5 | 
6 | [](LICENSE)
7 | [](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 | 
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
--------------------------------------------------------------------------------