Skip to content
ColorTokensKitby Penguin Design Ventures
Contents
Function

.toRGB()

Read a color's red, green and blue channels, even past sRGB, to hand to other graphics code.

Updated View as Markdown

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:

swift
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:

swift
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 0

To 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:

swift
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:

swift
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#

swift
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#

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.