Skip to content

Instantly share code, notes, and snippets.

@shaps80
Created April 26, 2024 09:38
Show Gist options
  • Save shaps80/e955bd97088e10c40b89c483f7e5b526 to your computer and use it in GitHub Desktop.
Save shaps80/e955bd97088e10c40b89c483f7e5b526 to your computer and use it in GitHub Desktop.
Includes RGB to HSV/HSB conversion.
import Swift
public struct Color: Sendable {
/// The red component of the color.
///
/// This property is not part of the public interface of the testing
/// library as we may wish to support non-RGB color spaces in the future.
internal var redComponent: UInt8
/// The green component of the color.
///
/// This property is not part of the public interface of the testing
/// library as we may wish to support non-RGB color spaces in the future.
internal var greenComponent: UInt8
/// The blue component of the color.
///
/// This property is not part of the public interface of the testing
/// library as we may wish to support non-RGB color spaces in the future.
internal var blueComponent: UInt8
/// The hue, saturation, and value (or brightness) components of the color.
///
/// This property is not part of the public interface of the testing
/// library as we may wish to support non-RGB color spaces in the future.
internal var hsvComponents: (hue: Float32, saturation: Float32, value: Float32) {
// Adapted from the algorithms at https://en.wikipedia.org/wiki/HSL_and_HSV
// including variable names.
let r = Float32(redComponent) / 255.0
let g = Float32(greenComponent) / 255.0
let b = Float32(blueComponent) / 255.0
let M = max(max(r, g), b)
let m = min(min(r, g), b)
let C = M - m
var H: Float32 = 0.0
if C > 0.0 {
if M == r {
H = (g - b) / C
} else if M == g {
H = ((b - r) / C) + 2.0
} else if M == b {
H = ((r - g) / C) + 4.0
}
H = H / 6.0
}
let V: Float32 = M
var Sv: Float32 = 0.0
if V > 0.0 {
Sv = C / V
}
return (H, Sv, V)
}
/// Get an instance of this type representing a custom color in the RGB
/// color space.
///
/// - Parameters:
/// - redComponent: The red component of the color.
/// - greenComponent: The green component of the color.
/// - blueComponent: The blue component of the color.
///
/// - Returns: An instance of this type representing the specified color.
public static func rgb(_ redComponent: UInt8, _ greenComponent: UInt8, _ blueComponent: UInt8) -> Self {
self.init(redComponent: redComponent, greenComponent: greenComponent, blueComponent: blueComponent)
}
}
extension Color: Equatable, Hashable {}
extension Color: Comparable {
public static func < (lhs: Self, rhs: Self) -> Bool {
// Compare by hue first as it will generally match human expectations for
// color ordering. Comparing by saturation before value is arbitrary.
let lhsHSV = lhs.hsvComponents
let rhsHSV = rhs.hsvComponents
if lhsHSV.hue != rhsHSV.hue {
return lhsHSV.hue < rhsHSV.hue
}
if lhsHSV.saturation != rhsHSV.saturation {
return lhsHSV.saturation < rhsHSV.saturation
}
return lhsHSV.value < rhsHSV.value
}
}
extension Color: Decodable {
public init(from decoder: any Decoder) throws {
let stringValue = try String(from: decoder)
switch stringValue {
case _ where stringValue.count == 7 && stringValue.first == "#":
guard let rgbValue = UInt32(stringValue.dropFirst(), radix: 16) else {
fallthrough
}
self = .rgb(
UInt8((rgbValue & 0x00FF_0000) >> 16),
UInt8((rgbValue & 0x0000_FF00) >> 8),
UInt8((rgbValue & 0x0000_00FF) >> 0)
)
default:
throw DecodingError.dataCorrupted(
DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "Unsupported color constant \"\(stringValue)\"."
)
)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment