1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
import SwiftUI
/// A color described by a hexadecimal color code.
///
/// `HEX` is a lightweight, string-backed representation: the code is stored
/// verbatim and parsed only when converted to a SwiftUI color via `Color(hex:)`
/// or `Color.hex(_:)`, which resolve it in the sRGB color space.
///
/// Supported formats (the leading `#` is optional):
/// - `#RGB`
/// - `#RGBA`
/// - `#RRGGBB`
/// - `#RRGGBBAA`
///
/// Single-digit components are expanded by repeating the digit; for example,
/// `#3aF` is read as `#33AAFF`. When present, the trailing digits encode alpha
/// (opacity); otherwise opacity defaults to `1`.
struct HEX {
/// The raw hex code, exactly as supplied. It is not validated or normalized;
/// parsing is performed lazily by `Color.hex(_:)`.
var code: String
/// Creates a hex color from the given code.
///
/// - Parameter code: A hex color code. Stored verbatim; see ``HEX`` for the
/// accepted formats.
init(code: String) {
self.code = code
}
}
extension SwiftUI.Color {
/// Creates a color from a ``HEX`` value.
init(hex: HEX) {
self = .hex(hex.code)
}
/// Creates a color from a hex code, resolving it in the sRGB color space.
///
/// Supported formats (the leading `#` is optional): `#RGB`, `#RGBA`,
/// `#RRGGBB`, and `#RRGGBBAA`. Single-digit components are expanded by
/// repeating the digit. When present, the trailing digits encode alpha
/// (opacity); otherwise opacity defaults to `1`. Malformed input resolves
/// to opaque black.
///
/// - Parameter code: A hex color code.
static func hex(_ code: String) -> SwiftUI.Color {
var sanitized = code.trimmingCharacters(in: .whitespacesAndNewlines)
if sanitized.hasPrefix("#") {
sanitized.removeFirst()
}
var rgba: UInt64 = 0
Scanner(string: sanitized).scanHexInt64(&rgba)
let red: Double
let green: Double
let blue: Double
let opacity: Double
switch sanitized.count {
case 8: // RRGGBBAA
red = Double((rgba >> 24) & 0xFF) / 255.0
green = Double((rgba >> 16) & 0xFF) / 255.0
blue = Double((rgba >> 8) & 0xFF) / 255.0
opacity = Double(rgba & 0xFF) / 255.0
case 6: // RRGGBB
red = Double((rgba >> 16) & 0xFF) / 255.0
green = Double((rgba >> 8) & 0xFF) / 255.0
blue = Double(rgba & 0xFF) / 255.0
opacity = 1.0
case 4: // RGBA
red = Double(((rgba >> 12) & 0xF) * 0x11) / 255.0
green = Double(((rgba >> 8) & 0xF) * 0x11) / 255.0
blue = Double(((rgba >> 4) & 0xF) * 0x11) / 255.0
opacity = Double((rgba & 0xF) * 0x11) / 255.0
case 3: // RGB
red = Double(((rgba >> 8) & 0xF) * 0x11) / 255.0
green = Double(((rgba >> 4) & 0xF) * 0x11) / 255.0
blue = Double((rgba & 0xF) * 0x11) / 255.0
opacity = 1.0
default:
red = 0.0
green = 0.0
blue = 0.0
opacity = 1.0
}
return Color(.sRGB, red: red, green: green, blue: blue, opacity: opacity)
}
}
extension SwiftUI.Color.Resolved {
/// The hex code for this resolved color.
///
/// Components are clamped to `0...1` and quantized to 8 bits.
/// The result is a 6-digit `#RRGGBB` code, or an 8-digit `#RRGGBBAA` code
/// when the color is not fully opaque.
var hex: HEX {
let r = Int((min(max(Double(red), 0), 1) * 255).rounded())
let g = Int((min(max(Double(green), 0), 1) * 255).rounded())
let b = Int((min(max(Double(blue), 0), 1) * 255).rounded())
let code: String
if opacity < 1 {
let a = Int((min(max(Double(opacity), 0), 1) * 255).rounded())
code = String(format: "#%02X%02X%02X%02X", r, g, b, a)
} else {
code = String(format: "#%02X%02X%02X", r, g, b)
}
return HEX(code: code)
}
}
|