Hand a color to Core Graphics, Metal, a chart library or a server with toRGB(). It takes nothing and returns an RGBColor: red, green, blue and alpha from 0 to 1, or past 0 and 1 for a color beyond sRGB:
Color(hex: "#00B386").toRGB() // RGBColor(r: 0, g: 0.702, b: 0.525, alpha: 1)
brand._600.toRGB() // RGBColor(r: -0.178, g: 0.527, b: 0.372, alpha: 1)Reading a stop beyond sRGB#
A palette stop beyond sRGB has a channel below 0 or above 1. That's extended sRGB: sRGB's channels, allowed past their ends for wider colors, and SwiftUI, UIKit and AppKit keep them that way. Hex can't, so getHexString() clamps each channel:
let fill = brand._600 // a stop of the travel app's brand, beyond sRGB
fill.toRGB() // RGBColor(r: -0.178, g: 0.527, b: 0.372)
fill.toColor().getHexString() // "00865F", with red clamped to 0To pass such a color on without losing it, keep its name or its toRGB() value, not its hex.
Back to a Color#
RGBColor has no toColor(). Make a Color with SwiftUI's own initializer, which keeps channels past 0 and 1:
let rgb = brand._600.toRGB()
let fill = Color(red: rgb.r, green: rgb.g, blue: rgb.b, opacity: rgb.alpha)Where UIKit is available, rgb.color() gives a UIColor instead.
Changing one channel#
with(r:g:b:alpha:) returns a copy with the values you pass and keeps the rest:
let rgb = Color(hex: "#00B386").toRGB()
let faded = rgb.with(alpha: 0.5) // RGBColor(r: 0, g: 0.702, b: 0.525, alpha: 0.5)How it behaves#
toRGB() reads the color as extended sRGB, so a Display P3 color comes through unchanged and every other conversion starts from these numbers. A token has two values, and toRGB() reads one: on iOS 17, macOS 14 and later, the light-mode value.
relativeLuminance gives the color's WCAG relative luminance, from 0 for black to 1 for white, and contrastRatio(to:method:) compares two colors with it. lerp(_:t:) mixes two RGBColor values in a straight line; .lerp(_:t:) compares the spaces to mix in.
From an RGBColor you can go to every other type: toXYZ(), toLAB(), toLCH(), toOKLab() and toOKLCH().
API#
public extension Color {
func toRGB() -> RGBColor
}
public struct RGBColor: Hashable, Sendable {
public let r: CGFloat // 0 to 1 inside sRGB, past them beyond it
public let g: CGFloat
public let b: CGFloat
public let alpha: CGFloat // 0 to 1
public init(r: CGFloat, g: CGFloat, b: CGFloat, alpha: CGFloat)
public init(color: Color)
public func with(
r: CGFloat? = nil, g: CGFloat? = nil, b: CGFloat? = nil, alpha: CGFloat? = nil
) -> RGBColor
public var relativeLuminance: CGFloat { get }
public func lerp(_ other: RGBColor, t: CGFloat) -> RGBColor
public func toXYZ() -> XYZColor
public func toLAB() -> LABColor
public func toLCH() -> LCHColor
public func toOKLab() -> OKLabColor
public func toOKLCH() -> OKLCHColor
public func color() -> UIColor // where UIKit is available
}toRGB() takes no parameters. ProTheme has it too, so a stop converts directly, as in brand._600.toRGB().
Sources#
- Color+ColorSpaces.swift, which defines
toRGB()onColor. - The RGB folder, which defines
RGBColor, and ContrastRatio.swift, which addsrelativeLuminance.
See also
- .getHexString() Log a color or hand it to a design tool as an sRGB hex string, with alpha when it's translucent.
- .toOKLCH() Read a color's lightness, chroma and hue the way the palette sees them, to compare or adjust it.
- .contrastRatio(to:method:) Check WCAG 2 and APCA contrast in Swift, and keep your color pairs passing with a unit test.
- Fitting colors into Display P3 Stay inside Display P3 without losing contrast: chroma drops, lightness holds.