Skip to content

Commit 45e2f21

Browse files
committed
Drawing: extend the Color interface to get color info
This adds the accessors for color information and associated tests. The accessors currently do not perform color space conversion.
1 parent 8a4ee62 commit 45e2f21

File tree

2 files changed

+227
-4
lines changed

2 files changed

+227
-4
lines changed

Sources/SwiftWin32/Drawing/Color.swift

Lines changed: 133 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ extension Color.Representation: Hashable {
6060
}
6161
}
6262

63+
/// An object that stores color data and sometimes opacity.
6364
public struct Color {
6465
private let value: Representation
6566

@@ -90,6 +91,134 @@ public struct Color {
9091
public init(dynamicProvider block: @escaping (TraitCollection) -> Color) {
9192
self.value = .func(block)
9293
}
94+
95+
// MARK - Getting the Color Information
96+
97+
/// Returns the components that form the color in the HSB color space.
98+
///
99+
/// If the color is in a compatible color space, it converts into the HSB
100+
/// color space, and its components return to your application. If the color
101+
/// isn’t in a compatible color space, the parameters don’t change.
102+
public func getHue(_ hue: UnsafeMutablePointer<Double>?,
103+
saturation: UnsafeMutablePointer<Double>?,
104+
brightness: UnsafeMutablePointer<Double>?,
105+
alpha: UnsafeMutablePointer<Double>?) -> Bool {
106+
switch self.value {
107+
case .gray:
108+
fatalError("cannot convert Gray to HSBA")
109+
case .rgba(let r, let g, let b, let a):
110+
func rgb2hsb(_ r: Double, _ g: Double, _ b: Double)
111+
-> (Double, Double, Double) {
112+
let V = max(r, g, b)
113+
let m = min(r, g, b)
114+
let delta = V - m
115+
116+
var hue: Double
117+
if V == 0.0 { hue = 0.0 }
118+
else if V == r { hue = (g - b) / delta }
119+
else if V == g { hue = 2.0 + (b - r) / delta }
120+
else if V == b { hue = 4.0 + (r - g) / delta }
121+
else { fatalError("maximum must be one of the values") }
122+
123+
hue = ucrt.fmodl(hue + 6.0, 6.0)
124+
return (hue == 0.0 ? 1.0 : hue / 6.0, V, V == 0.0 ? 0.0 : delta / V)
125+
}
126+
127+
let (h, s, b) = rgb2hsb(r, g, b)
128+
129+
hue?.pointee = h
130+
brightness?.pointee = s
131+
saturation?.pointee = b
132+
alpha?.pointee = a
133+
return true
134+
case .hsba(let h, let s, let b, let a):
135+
hue?.pointee = h
136+
saturation?.pointee = s
137+
brightness?.pointee = b
138+
alpha?.pointee = a
139+
return true
140+
case .func:
141+
return resolvedColor(with: TraitCollection.current)
142+
.getHue(hue, saturation: saturation, brightness: brightness,
143+
alpha: alpha)
144+
}
145+
}
146+
147+
/// Returns the components that form the color in the RGB color space.
148+
///
149+
/// If the color is in a compatible color space, it converts into RGB format
150+
/// and its components return to your application. If the color isn’t in a
151+
/// compatible color space, the parameters don’t change.
152+
public func getRed(_ red: UnsafeMutablePointer<Double>?,
153+
green: UnsafeMutablePointer<Double>?,
154+
blue: UnsafeMutablePointer<Double>?,
155+
alpha: UnsafeMutablePointer<Double>?) -> Bool {
156+
switch self.value {
157+
case .gray:
158+
fatalError("cannot convert Gray to RGBA")
159+
case .rgba(let r, let g, let b, let a):
160+
red?.pointee = r
161+
green?.pointee = g
162+
blue?.pointee = b
163+
alpha?.pointee = a
164+
return true
165+
case .hsba(let h, let s, let b, let a):
166+
func f(_ n: Double) -> Double {
167+
let k = ucrt.fmod(n + (6.0 * h), 6.0)
168+
return b - b * s * max(0, min(k, 4 - k, 1))
169+
}
170+
red?.pointee = f(5.0)
171+
green?.pointee = f(3.0)
172+
blue?.pointee = f(1.0)
173+
alpha?.pointee = a
174+
return true
175+
case .func:
176+
return resolvedColor(with: TraitCollection.current)
177+
.getRed(red, green: green, blue: blue, alpha: alpha)
178+
}
179+
}
180+
181+
/// Returns the grayscale components of the color.
182+
///
183+
/// If the color is in a compatible color space, it converts into grayscale
184+
/// format and its returned to your application. If the color isn’t in a
185+
/// compatible color space, the parameters don’t change.
186+
public func getWhite(_ white: UnsafeMutablePointer<Double>?,
187+
alpha: UnsafeMutablePointer<Double>?) -> Bool {
188+
switch self.value {
189+
case .gray(let w, let a):
190+
white?.pointee = w
191+
alpha?.pointee = a
192+
return true
193+
case .rgba(let r, let g, let b, let a):
194+
// The weighted or luminosity method uses the weighted average of the
195+
// channels based on the wavelength of the channel.
196+
// The constants here are from ITU-R BT.601-7.
197+
white?.pointee = r * 0.299 + g * 0.587 + b * 0.114
198+
alpha?.pointee = a
199+
return true
200+
case .hsba(let h, let s, let b, let a):
201+
func f(_ n: Double) -> Double {
202+
let k = ucrt.fmod(n + (6.0 * h), 6.0)
203+
return b - b * s * max(0, min(k, 4 - k, 1))
204+
}
205+
white?.pointee = f(5.0) * 0.299 + f(3.0) * 0.587 + f(1.0) * 0.114
206+
alpha?.pointee = a
207+
return true
208+
case .func:
209+
return resolvedColor(with: TraitCollection.current)
210+
.getWhite(white, alpha: alpha)
211+
}
212+
}
213+
214+
// MARK - Resolving a Dynamically Generated Color
215+
216+
/// Returns the version of the current color that results from the specified
217+
/// traits.
218+
public func resolvedColor(with traitCollection: TraitCollection) -> Color {
219+
guard case let .func(body) = self.value else { return self }
220+
return body(traitCollection)
221+
}
93222
}
94223

95224
extension COLORREF {
@@ -115,8 +244,8 @@ extension Color {
115244
return WinSDK.COLORREF(red: f(5.0), green: f(3.0), blue: f(1.0))
116245
case .gray(let w, _):
117246
return WinSDK.COLORREF(red: w * 255.0, green: w * 255.0, blue: w * 255.0)
118-
case .func(let body):
119-
return body(TraitCollection.current).COLORREF
247+
case .func:
248+
return self.resolvedColor(with: TraitCollection.current).COLORREF
120249
}
121250
}
122251
}
@@ -150,14 +279,14 @@ extension Color {
150279
}
151280

152281
extension Color {
153-
internal init(red: Int, green: Int, blue: Int,
154-
alpha: Double = 1.0) {
282+
internal init(red: Int, green: Int, blue: Int, alpha: Double = 1.0) {
155283
self.init(red: Double(red) / 255.0, green: Double(green) / 255.0,
156284
blue: Double(blue) / 255.0, alpha: Double(alpha))
157285
}
158286
}
159287

160288
// MARK - Standard Colors
289+
161290
extension Color {
162291
// MARK - Adaptable Colors
163292

Tests/UICoreTests/ColorTests.swift

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright © 2021 Saleem Abulrasool <[email protected]>
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
4+
import SwiftWin32
5+
import XCTest
6+
7+
final class ColorTests: XCTestCase {
8+
func testGetColorComponentsRGBA() {
9+
let rgba: Color = Color(red: 0.1, green: 0.3, blue: 0.7, alpha: 1.0)
10+
let dyn: Color = Color(dynamicProvider: { _ in rgba })
11+
let hsba: Color =
12+
Color(hue: 0.61, saturation: 0.85, brightness: 0.70, alpha: 1.0)
13+
14+
XCTAssertTrue(rgba.getRed(nil, green: nil, blue: nil, alpha: nil))
15+
16+
var red: Double = 0.0, green: Double = 0.0, blue: Double = 0.0, alpha: Double = 0.0
17+
XCTAssertTrue(rgba.getRed(&red, green: &green, blue: &blue, alpha: &alpha))
18+
XCTAssertEqual(red, 0.1)
19+
XCTAssertEqual(green, 0.3)
20+
XCTAssertEqual(blue, 0.7)
21+
XCTAssertEqual(alpha, 1.0)
22+
23+
XCTAssertTrue(dyn.getRed(&red, green: &green, blue: &blue, alpha: &alpha))
24+
XCTAssertEqual(red, 0.1)
25+
XCTAssertEqual(green, 0.3)
26+
XCTAssertEqual(blue, 0.7)
27+
XCTAssertEqual(alpha, 1.0)
28+
29+
XCTAssertTrue(hsba.getRed(&red, green: &green, blue: &blue, alpha: &alpha))
30+
XCTAssertEqual(red, 0.1, accuracy: 0.01)
31+
XCTAssertEqual(green, 0.3, accuracy: 0.01)
32+
XCTAssertEqual(blue, 0.7)
33+
XCTAssertEqual(alpha, 1.0)
34+
}
35+
36+
func testGetColorComponentsHSBA() {
37+
let hsba: Color =
38+
Color(hue: 0.61, saturation: 0.85, brightness: 0.70, alpha: 1.0)
39+
let dyn: Color = Color(dynamicProvider: { _ in hsba })
40+
let rgba: Color = Color(red: 0.1, green: 0.3, blue: 0.7, alpha: 1.0)
41+
42+
XCTAssertTrue(hsba.getHue(nil, saturation: nil, brightness: nil, alpha: nil))
43+
44+
var hue: Double = 0.0, saturation: Double = 0.0, brightness: Double = 0.0, alpha: Double = 0.0
45+
XCTAssertTrue(hsba.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha))
46+
XCTAssertEqual(hue, 0.61)
47+
XCTAssertEqual(saturation, 0.85)
48+
XCTAssertEqual(brightness, 0.70)
49+
XCTAssertEqual(alpha, 1.0)
50+
51+
XCTAssertTrue(dyn.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha))
52+
XCTAssertEqual(hue, 0.61)
53+
XCTAssertEqual(saturation, 0.85)
54+
XCTAssertEqual(brightness, 0.70)
55+
XCTAssertEqual(alpha, 1.0)
56+
57+
XCTAssertTrue(rgba.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha))
58+
XCTAssertEqual(hue, 0.61, accuracy: 0.01)
59+
XCTAssertEqual(saturation, 0.85, accuracy: 0.01)
60+
XCTAssertEqual(brightness, 0.70)
61+
XCTAssertEqual(alpha, 1.0)
62+
}
63+
64+
func testGetColorComponentsGrey() {
65+
let grey: Color = Color(white: 0.39, alpha: 1.0)
66+
let dyn: Color = Color(dynamicProvider: { _ in grey })
67+
let rgba: Color = Color(red: 0.1, green: 0.3, blue: 0.7, alpha: 1.0)
68+
let hsba: Color =
69+
Color(hue: 0.61, saturation: 0.85, brightness: 0.70, alpha: 1.0)
70+
71+
XCTAssertTrue(grey.getWhite(nil, alpha: nil))
72+
73+
var white: Double = 0.0, alpha: Double = 0.0
74+
XCTAssertTrue(grey.getWhite(&white, alpha: &alpha))
75+
XCTAssertEqual(white, 0.39)
76+
XCTAssertEqual(alpha, 1.0)
77+
78+
XCTAssertTrue(dyn.getWhite(&white, alpha: &alpha))
79+
80+
XCTAssertTrue(rgba.getWhite(&white, alpha: &alpha))
81+
XCTAssertEqual(white, 0.29, accuracy: 0.01)
82+
XCTAssertEqual(alpha, 1.0)
83+
84+
XCTAssertTrue(hsba.getWhite(&white, alpha: &alpha))
85+
XCTAssertEqual(white, 0.29, accuracy: 0.01)
86+
XCTAssertEqual(alpha, 1.0)
87+
}
88+
89+
static var allTests = [
90+
("testGetColorComponentsRGBA", testGetColorComponentsRGBA),
91+
("testGetColorComponentsHSBA", testGetColorComponentsHSBA),
92+
("testGetColorComponentsGrey", testGetColorComponentsGrey),
93+
]
94+
}

0 commit comments

Comments
 (0)