Created
April 26, 2024 09:38
-
-
Save shaps80/e955bd97088e10c40b89c483f7e5b526 to your computer and use it in GitHub Desktop.
Includes RGB to HSV/HSB conversion.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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