How to compute color contrast ratio between two UIColor instances

If building for WatchOS where CoreImage is not available or for whatever reason you do not want to rely on CoreImage and therefore CIColor is unavailable, replace @Mobile Dan's luminance function with:

  private func luminance() -> CGFloat {
  // https://www.w3.org/TR/WCAG20-TECHS/G18.html#G18-tests

    func adjust(colorComponent: CGFloat) -> CGFloat {
      return (colorComponent < 0.03928) ? (colorComponent / 12.92) : pow((colorComponent + 0.055) / 1.055, 2.4)
    }

    var red: CGFloat = 0
    var green: CGFloat = 0
    var blue: CGFloat = 0
    var alpha: CGFloat = 0
    getRed(&red, green: &green, blue: &blue, alpha: &alpha)

    return 0.2126 * adjust(colorComponent: red)
      + 0.7152 * adjust(colorComponent: green)
      + 0.0722 * adjust(colorComponent: blue)
  }

UIColor extension with contrast ratio and luminance

The following UIColor extension includes a static and instance contrast ratio method. A bonus luminance method is included since it is used by the static contrastRatio(between:and:) method.

import UIKit

extension UIColor {

    static func contrastRatio(between color1: UIColor, and color2: UIColor) -> CGFloat {
        // https://www.w3.org/TR/WCAG20-TECHS/G18.html#G18-tests

        let luminance1 = color1.luminance()
        let luminance2 = color2.luminance()

        let luminanceDarker = min(luminance1, luminance2)
        let luminanceLighter = max(luminance1, luminance2)

        return (luminanceLighter + 0.05) / (luminanceDarker + 0.05)
    }

    func contrastRatio(with color: UIColor) -> CGFloat {
        return UIColor.contrastRatio(between: self, and: color)
    }

    func luminance() -> CGFloat {
        // https://www.w3.org/TR/WCAG20-TECHS/G18.html#G18-tests

        let ciColor = CIColor(color: self)

        func adjust(colorComponent: CGFloat) -> CGFloat {
            return (colorComponent < 0.04045) ? (colorComponent / 12.92) : pow((colorComponent + 0.055) / 1.055, 2.4)
        }

        return 0.2126 * adjust(colorComponent: ciColor.red) + 0.7152 * adjust(colorComponent: ciColor.green) + 0.0722 * adjust(colorComponent: ciColor.blue)
    }
}

Example Use

// static method
let contrastRatio1 = UIColor.contrastRatio(between: UIColor.black, and: UIColor.white)
print(contrastRatio1) // 21.0

// instance method
let contrastRatio2 = UIColor.black.contrastRatio(with: UIColor.white)
print(contrastRatio2) // 21.0

Note

Following these links:

  • https://www.w3.org/TR/css-color-4/#predefined
  • https://github.com/dequelabs/axe-core/issues/1629#issuecomment-509880306

For predefinite colorspace (like in iOS see this https://developer.apple.com/videos/play/wwdc2016/712/) and also in general the correct THRESHOLD value is 0.04045 and not 0.03928 (read more)

Tags:

Ios

Uicolor

Swift