Skip to content

Commit 6b7d495

Browse files
committed
Initialization with XYZ components
1 parent 4bc264e commit 6b7d495

10 files changed

+145
-84
lines changed

CHANGELOG.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# Change log
22

3-
## Version 3.0.1
3+
## Version 3.1.0
44

55
- [ADD] CIE XYZ Color Space
6-
- `toXYZAComponents()` method
6+
- Initialization with XYZ components
7+
- `toXYZComponents()` method
8+
- [REFACTORING] `toHSLAComponents` to `toHSLComponents`
79

810
## [Version 3.0.0](https://github.com/yannickl/DynamicColor/releases/tag/3.0.0)
911
*Released on 2016-06-14.*

Examples/DynamicColorExample.xcodeproj/project.pbxproj

+12
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
CE5D64771D341198005DEE4E /* DynamicColorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CECBCDA71B2B6EEB0047E731 /* DynamicColorTests.swift */; };
1313
CE5D64781D34119A005DEE4E /* DynamicColor+HSLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8B86431D29875E00C5A670 /* DynamicColor+HSLTests.swift */; };
1414
CE5D64791D34119C005DEE4E /* DynamicColor+RGBATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8B86441D29875E00C5A670 /* DynamicColor+RGBATests.swift */; };
15+
CE5D647B1D35074B005DEE4E /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5D647A1D35074B005DEE4E /* Utils.swift */; };
16+
CE5D647C1D35074B005DEE4E /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5D647A1D35074B005DEE4E /* Utils.swift */; };
17+
CE5D647D1D35074B005DEE4E /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5D647A1D35074B005DEE4E /* Utils.swift */; };
18+
CE5D647E1D35074B005DEE4E /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5D647A1D35074B005DEE4E /* Utils.swift */; };
19+
CE5D647F1D350754005DEE4E /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5D647A1D35074B005DEE4E /* Utils.swift */; };
1520
CE8B86451D29875E00C5A670 /* DynamicColor+HSLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8B86431D29875E00C5A670 /* DynamicColor+HSLTests.swift */; };
1621
CE8B86461D29875E00C5A670 /* DynamicColor+RGBATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8B86441D29875E00C5A670 /* DynamicColor+RGBATests.swift */; };
1722
CE8B86481D29884400C5A670 /* XCTTestCaseTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8B86471D29884400C5A670 /* XCTTestCaseTemplate.swift */; };
@@ -99,6 +104,7 @@
99104

100105
/* Begin PBXFileReference section */
101106
CE5D64741D33F81B005DEE4E /* DynamicColor+XYZTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "DynamicColor+XYZTests.swift"; path = "../../Tests/DynamicColor+XYZTests.swift"; sourceTree = "<group>"; };
107+
CE5D647A1D35074B005DEE4E /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
102108
CE8B86431D29875E00C5A670 /* DynamicColor+HSLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "DynamicColor+HSLTests.swift"; path = "../../Tests/DynamicColor+HSLTests.swift"; sourceTree = "<group>"; };
103109
CE8B86441D29875E00C5A670 /* DynamicColor+RGBATests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "DynamicColor+RGBATests.swift"; path = "../../Tests/DynamicColor+RGBATests.swift"; sourceTree = "<group>"; };
104110
CE8B86471D29884400C5A670 /* XCTTestCaseTemplate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCTTestCaseTemplate.swift; sourceTree = "<group>"; };
@@ -240,6 +246,7 @@
240246
CEAF678C1CA2D342008DC3A2 /* DynamicColor+RGBA.swift */,
241247
CE8B86541D2991F000C5A670 /* DynamicColor+XYZ.swift */,
242248
CEF85A241C84EA5B00DD1A49 /* HSL.swift */,
249+
CE5D647A1D35074B005DEE4E /* Utils.swift */,
243250
);
244251
name = DynamicColor;
245252
path = ../Sources;
@@ -504,6 +511,7 @@
504511
CEF85A251C84EA5B00DD1A49 /* DynamicColor.swift in Sources */,
505512
CEAF678D1CA2D342008DC3A2 /* DynamicColor+RGBA.swift in Sources */,
506513
CEF02EED1D2984CD00D810B9 /* DynamicColor+HSL.swift in Sources */,
514+
CE5D647B1D35074B005DEE4E /* Utils.swift in Sources */,
507515
CE8B86551D2991F000C5A670 /* DynamicColor+XYZ.swift in Sources */,
508516
CEAF67871CA2D22E008DC3A2 /* DynamicColor+Deriving.swift in Sources */,
509517
CEF85A261C84EA5B00DD1A49 /* HSL.swift in Sources */,
@@ -525,6 +533,7 @@
525533
CEAF67901CA2D342008DC3A2 /* DynamicColor+RGBA.swift in Sources */,
526534
CE8B86451D29875E00C5A670 /* DynamicColor+HSLTests.swift in Sources */,
527535
CE5D64751D33F81B005DEE4E /* DynamicColor+XYZTests.swift in Sources */,
536+
CE5D647E1D35074B005DEE4E /* Utils.swift in Sources */,
528537
);
529538
runOnlyForDeploymentPostprocessing = 0;
530539
};
@@ -543,6 +552,7 @@
543552
CEF85A2D1C84EA6800DD1A49 /* DynamicColor.swift in Sources */,
544553
CE5D64761D341105005DEE4E /* DynamicColor+XYZTests.swift in Sources */,
545554
CE5D64791D34119C005DEE4E /* DynamicColor+RGBATests.swift in Sources */,
555+
CE5D647F1D350754005DEE4E /* Utils.swift in Sources */,
546556
);
547557
runOnlyForDeploymentPostprocessing = 0;
548558
};
@@ -555,6 +565,7 @@
555565
CEF85A291C84EA6700DD1A49 /* DynamicColor.swift in Sources */,
556566
CEAF678F1CA2D342008DC3A2 /* DynamicColor+RGBA.swift in Sources */,
557567
CEEB28E01BE27401001A74E8 /* ColorCellView.swift in Sources */,
568+
CE5D647D1D35074B005DEE4E /* Utils.swift in Sources */,
558569
CEAF67891CA2D22E008DC3A2 /* DynamicColor+Deriving.swift in Sources */,
559570
CE8B86571D2991F000C5A670 /* DynamicColor+XYZ.swift in Sources */,
560571
CEEB28D21BE27340001A74E8 /* AppDelegate.swift in Sources */,
@@ -571,6 +582,7 @@
571582
CEF85A271C84EA6600DD1A49 /* DynamicColor.swift in Sources */,
572583
CEAF678E1CA2D342008DC3A2 /* DynamicColor+RGBA.swift in Sources */,
573584
CEFBDAE31B1CE38D000E6F30 /* ViewController.swift in Sources */,
585+
CE5D647C1D35074B005DEE4E /* Utils.swift in Sources */,
574586
CEAF67881CA2D22E008DC3A2 /* DynamicColor+Deriving.swift in Sources */,
575587
CE8B86561D2991F000C5A670 /* DynamicColor+XYZ.swift in Sources */,
576588
CEFBDAE11B1CE38D000E6F30 /* AppDelegate.swift in Sources */,

Sources/DynamicColor+HSL.swift

+8-7
Original file line numberDiff line numberDiff line change
@@ -39,25 +39,26 @@ extension DynamicColor {
3939
- parameter hue: The hue component of the color object, specified as a value from 0.0 to 1.0 (0.0 for 0 degree and 1.0 for 360 degree).
4040
- parameter saturation: The saturation component of the color object, specified as a value from 0.0 to 1.0.
4141
- parameter lightness: The lightness component of the color object, specified as a value from 0.0 to 1.0.
42-
- parameter alpha: The opacity value of the color object, specified as a value from 0.0 to 1.0.
4342
*/
44-
public convenience init(hue: Double, saturation: Double, lightness: Double, alpha: Double = 1) {
45-
let color = HSL(hue: hue, saturation: saturation, lightness: lightness, alpha: alpha).toDynamicColor()
43+
public convenience init(hue: Double, saturation: Double, lightness: Double) {
44+
let color = HSL(hue: hue, saturation: saturation, lightness: lightness, alpha: 1).toDynamicColor()
4645
let components = color.toRGBAComponents()
4746

4847
self.init(red: components.r, green: components.g, blue: components.b, alpha: components.a)
4948
}
5049

50+
// MARK: - Getting the HSL Components
51+
5152
/**
52-
Returns the HSLA (hue, saturation, lightness, alpha) components.
53+
Returns the HSLA (hue, saturation, lightness) components.
5354

5455
Notes that the hue value is between 0.0 and 1.0 (0.0 for 0 degree and 1.0 for 360 degree).
5556

56-
- returns: The HSLA components as a tuple (h, s, l, a).
57+
- returns: The HSLA components as a tuple (h, s, l).
5758
*/
58-
public final func toHSLAComponents() -> (h: CGFloat, s: CGFloat, l: CGFloat, a: CGFloat) {
59+
public final func toHSLComponents() -> (h: CGFloat, s: CGFloat, l: CGFloat) {
5960
let hsl = HSL(color: self)
6061

61-
return (CGFloat(hsl.h), CGFloat(hsl.s), CGFloat(hsl.l), CGFloat(hsl.a))
62+
return (CGFloat(hsl.h), CGFloat(hsl.s), CGFloat(hsl.l))
6263
}
6364
}

Sources/DynamicColor+RGBA.swift

+4-12
Original file line numberDiff line numberDiff line change
@@ -70,36 +70,28 @@ public extension DynamicColor {
7070
The red component as CGFloat between 0.0 to 1.0.
7171
*/
7272
public final var redComponent: CGFloat {
73-
get {
74-
return toRGBAComponents().r
75-
}
73+
return toRGBAComponents().r
7674
}
7775

7876
/**
7977
The green component as CGFloat between 0.0 to 1.0.
8078
*/
8179
public final var greenComponent: CGFloat {
82-
get {
83-
return toRGBAComponents().g
84-
}
80+
return toRGBAComponents().g
8581
}
8682

8783
/**
8884
The blue component as CGFloat between 0.0 to 1.0.
8985
*/
9086
public final var blueComponent: CGFloat {
91-
get {
92-
return toRGBAComponents().b
93-
}
87+
return toRGBAComponents().b
9488
}
9589

9690
/**
9791
The alpha component as CGFloat between 0.0 to 1.0.
9892
*/
9993
public final var alphaComponent: CGFloat {
100-
get {
101-
return toRGBAComponents().a
102-
}
94+
return toRGBAComponents().a
10395
}
10496
#endif
10597

Sources/DynamicColor+XYZ.swift

+38-19
Original file line numberDiff line numberDiff line change
@@ -34,36 +34,55 @@
3434

3535
public extension DynamicColor {
3636
/**
37-
Returns the XYZA (mix of cone response curves, luminance, quasi-equal to blue stimulation, alpha) components.
37+
Initializes and returns a color object using CIE XYZ color space component values.
38+
39+
Notes that values out of range are clipped.
3840

39-
Notes that values are between 0.0 and 1.0.
41+
- parameter X: The mix of cone response curves, specified as a value from 0 to 95.05.
42+
- parameter Y: The luminance, specified as a value from 0 to 100.0.
43+
- parameter Z: The quasi-equal to blue stimulation, specified as a value from 0 to 108.9.
44+
*/
45+
public convenience init(X: CGFloat, Y: CGFloat, Z: CGFloat) {
46+
let clippedX = clip(X, 0, 95.05) / 100
47+
let clippedY = clip(Y, 0, 100) / 100
48+
let clippedZ = clip(Z, 0, 108.9) / 100
49+
50+
let toRGB = { (c: CGFloat) -> CGFloat in
51+
let rgb = c > 0.0031308 ? 1.055 * pow(c, 1 / 2.4) - 0.055 : 12.92 * c
52+
53+
return CGFloat(Int(rgb * 10000) / 10000)
54+
}
55+
56+
let red = toRGB(clippedX * 3.2406 + clippedY * -1.5372 + clippedZ * -0.4986)
57+
let green = toRGB(clippedX * -0.9689 + clippedY * 1.8758 + clippedZ * 0.0415)
58+
let blue = toRGB(clippedX * 0.0557 + clippedY * -0.2040 + clippedZ * 1.0570)
59+
60+
self.init(red: red, green: green, blue: blue, alpha: 1)
61+
}
62+
63+
// MARK: - Getting the XYZ Components
64+
65+
/**
66+
Returns the XYZ (mix of cone response curves, luminance, quasi-equal to blue stimulation) components with observer at 2° and the D65 illuminant.
67+
68+
Notes that X values are between 0 to 95.05, Y values are between 0 to 100.0 and Z values are between 0 to 108.9.
4069

41-
- returns: The XYZA components as a tuple (X, Y, Z, A).
70+
- returns: The XYZA components as a tuple (X, Y, Z).
4271
*/
43-
public final func toXYZAComponents() -> (X: CGFloat, Y: CGFloat, Z: CGFloat, A: CGFloat) {
44-
/*
45-
Converts RGB to sRGB.
46-
We're going to use the Reverse Transformation Equation.
47-
http://en.wikipedia.org/wiki/SRGB
48-
*/
49-
let sRGB = { (c: CGFloat) -> CGFloat in
72+
public final func toXYZComponents() -> (X: CGFloat, Y: CGFloat, Z: CGFloat) {
73+
let toSRGB = { (c: CGFloat) -> CGFloat in
5074
c > 0.04045 ? pow((c + 0.055) / (1 + 0.055), 2.40) : c / 12.92
5175
}
5276

5377
let rgba = toRGBAComponents()
54-
let red = sRGB(rgba.r)
55-
let green = sRGB(rgba.g)
56-
let blue = sRGB(rgba.b)
78+
let red = toSRGB(rgba.r)
79+
let green = toSRGB(rgba.g)
80+
let blue = toSRGB(rgba.b)
5781

58-
/*
59-
Converts to XYZ values
60-
Using a matrix multiplication of the linear values
61-
http://upload.wikimedia.org/math/4/3/3/433376fc18cccd887758beffb7e7c625.png
62-
*/
6382
let X = Int((red * 0.4124 + green * 0.3576 + blue * 0.1805) * 100000)
6483
let Y = Int((red * 0.2126 + green * 0.7152 + blue * 0.0722) * 100000)
6584
let Z = Int((red * 0.0193 + green * 0.1192 + blue * 0.9505) * 100000)
6685

67-
return (X: CGFloat(X) / 1000, Y: CGFloat(Y) / 1000, Z: CGFloat(Z) / 1000, A: rgba.a)
86+
return (X: CGFloat(X) / 1000, Y: CGFloat(Y) / 1000, Z: CGFloat(Z) / 1000)
6887
}
6988
}

Sources/HSL.swift

-25
Original file line numberDiff line numberDiff line change
@@ -30,31 +30,6 @@
3030
import AppKit
3131
#endif
3232

33-
/**
34-
Clips the values in an interval.
35-
36-
Given an interval, values outside the interval are clipped to the interval
37-
edges. For example, if an interval of [0, 1] is specified, values smaller than
38-
0 become 0, and values larger than 1 become 1.
39-
40-
- parameter v: The value to clipped.
41-
- parameter minimum: The minimum edge value.
42-
- parameter maximum: The maximum edgevalue.
43-
*/
44-
internal func clip<T: Comparable>(_ v: T, _ minimum: T, _ maximum: T) -> T {
45-
return max(min(v, maximum), minimum)
46-
}
47-
48-
/**
49-
Returns the absolute value of the modulo operation.
50-
51-
- parameter x: The value to compute.
52-
- parameter m: The modulo.
53-
*/
54-
internal func moda(_ x: Double, m: Double) -> Double {
55-
return (x.truncatingRemainder(dividingBy: m) + m).truncatingRemainder(dividingBy: m)
56-
}
57-
5833
/// Hue-saturation-lightness structure to make the color manipulation easier.
5934
internal struct HSL {
6035
/// Hue value between 0.0 and 1.0 (0.0 = 0 degree, 1.0 = 360 degree).

Sources/Utils.swift

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* DynamicColor
3+
*
4+
* Copyright 2015-present Yannick Loriot.
5+
* http://yannickloriot.com
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*
25+
*/
26+
27+
import Foundation
28+
29+
/**
30+
Clips the values in an interval.
31+
32+
Given an interval, values outside the interval are clipped to the interval
33+
edges. For example, if an interval of [0, 1] is specified, values smaller than
34+
0 become 0, and values larger than 1 become 1.
35+
36+
- parameter v: The value to clipped.
37+
- parameter minimum: The minimum edge value.
38+
- parameter maximum: The maximum edgevalue.
39+
*/
40+
internal func clip<T: Comparable>(_ v: T, _ minimum: T, _ maximum: T) -> T {
41+
return max(min(v, maximum), minimum)
42+
}
43+
44+
/**
45+
Returns the absolute value of the modulo operation.
46+
47+
- parameter x: The value to compute.
48+
- parameter m: The modulo.
49+
*/
50+
internal func moda(_ x: Double, m: Double) -> Double {
51+
return (x.truncatingRemainder(dividingBy: m) + m).truncatingRemainder(dividingBy: m)
52+
}

Tests/DynamicColor+HSLTests.swift

+6-10
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import XCTest
2828

2929
class DynamicColorHSLTests: XCTTestCaseTemplate {
30-
func testInitWithHSLAComponents() {
30+
func testInitWithHSLComponents() {
3131
let black1 = DynamicColor(hue: 0, saturation: 0, lightness: 0)
3232
let black2 = DynamicColor(hue: 1, saturation: 1, lightness: 0)
3333
let white1 = DynamicColor(hue: 0, saturation: 0, lightness: 1)
@@ -38,7 +38,6 @@ class DynamicColorHSLTests: XCTTestCaseTemplate {
3838
let blue = DynamicColor(hue: 240 / 360, saturation: 1, lightness: 0.5)
3939

4040
let custom = DynamicColor(hue: 6 / 360, saturation: 0.781, lightness: 0.571)
41-
let alpha = DynamicColor(hue: 6 / 360, saturation: 0.781, lightness: 0.571, alpha: 0.5)
4241

4342
XCTAssert(black1.toHex() == 0, "Color should be black")
4443
XCTAssert(black2.toHex() == 0, "Color should be black")
@@ -50,17 +49,14 @@ class DynamicColorHSLTests: XCTTestCaseTemplate {
5049
XCTAssert(blue.isEqual(DynamicColor.blue()), "Color should be blue")
5150

5251
XCTAssert(custom.isEqualToHexString("#e74d3c"), "Color should be equal to #e74d3c")
53-
XCTAssert(alpha.isEqualToHexString("#e74d3c"), "Color should be equal to #e74d3c")
54-
XCTAssert(alpha.alphaComponent == 0.5, "Color alpha channel should be equal to 0.5")
5552
}
5653

57-
func testToHSLAComponents() {
54+
func testToHSLComponents() {
5855
let customColor = DynamicColor(hue: 6 / 360, saturation: 0.781, lightness: 0.571)
59-
let hsla = customColor.toHSLAComponents()
56+
let hsl = customColor.toHSLComponents()
6057

61-
XCTAssert(round(hsla.h * 1000) == round(6.0 / 360 * 1000), "Color hue component should be equal to 6 / 360")
62-
XCTAssert(round(hsla.s * 1000) == round(0.781 * 1000), "Color saturation component should be equal to 0.781")
63-
XCTAssert(hsla.l == 0.571, "Color lightness component should be equal to 0.571")
64-
XCTAssert(hsla.a == 1, "Color alpha component should be equal to 1")
58+
XCTAssert(round(hsl.h * 1000) == round(6.0 / 360 * 1000), "Color hue component should be equal to 6 / 360")
59+
XCTAssert(round(hsl.s * 1000) == round(0.781 * 1000), "Color saturation component should be equal to 0.781")
60+
XCTAssert(hsl.l == 0.571, "Color lightness component should be equal to 0.571")
6561
}
6662
}

0 commit comments

Comments
 (0)