Skip to content

Instantly share code, notes, and snippets.

@lkoskela
Created March 30, 2017 11:46
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lkoskela/c06670ded4d01a1832bd90066e76a0a8 to your computer and use it in GitHub Desktop.
Save lkoskela/c06670ded4d01a1832bd90066e76a0a8 to your computer and use it in GitHub Desktop.
Utility for calculating the relative luminance and contrast ratio of two colors to determine whether they meet the recommendation set forth by the W3's Web Content Accessibility Guidelines 2.0.
import UIKit
extension UIFont {
private var weight: CGFloat {
if let traits = fontDescriptor.object(forKey: UIFontDescriptorTraitsAttribute) as? NSDictionary,
let weight = traits[UIFontWeightTrait] as? CGFloat {
return weight
}
return 0
}
var isBold: Bool {
return fontDescriptor.symbolicTraits.contains(.traitBold) || weight > CGFloat(0.0)
}
}
extension UIColor {
/// Relative luminance of a color according to W3's WCAG 2.0:
/// https://www.w3.org/TR/WCAG20/#relativeluminancedef
var luminance: CGFloat {
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0
self.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
return 0.2126 * red + 0.7152 * green + 0.0722 * blue
}
/// Contrast ratio between two colors according to W3's WCAG 2.0:
/// https://www.w3.org/TR/WCAG20/#contrast-ratiodef
func contrastRatio(to otherColor: UIColor) -> CGFloat {
let ourLuminance = self.luminance
let theirLuminance = otherColor.luminance
let lighterColor = min(ourLuminance, theirLuminance)
let darkerColor = max(ourLuminance, theirLuminance)
return 1 / ((lighterColor + 0.05) / (darkerColor + 0.05))
}
/// Determines whether the contrast between this `UIColor` and the provided
/// `UIColor` is sufficient to meet the recommendations of W3's WCAG 2.0.
///
/// The recommendation is that the contrast ratio between text and its
/// background should be at least 4.5:1 for small text and at least
/// 3.0:1 for larger text.
func sufficientContrast(to otherColor: UIColor, withFont font: UIFont = UIFont.systemFont(ofSize: UIFont.smallSystemFontSize)) -> Bool {
let pointSizeThreshold: CGFloat = font.isBold ? 14.0 : 18.0
let contrastRatioThreshold: CGFloat = font.fontDescriptor.pointSize < pointSizeThreshold ? 4.5 : 3.0
return contrastRatio(to: otherColor) > contrastRatioThreshold
}
}
@atrepeklis
Copy link

atrepeklis commented Oct 1, 2019

Thanks for sharing the code, just for your information:

As noted in the link you are referring to the RGB values are:
if RsRGB <= 0.03928 then R = RsRGB/12.92 else R = ((RsRGB+0.055)/1.055) ^ 2.4
if GsRGB <= 0.03928 then G = GsRGB/12.92 else G = ((GsRGB+0.055)/1.055) ^ 2.4
if BsRGB <= 0.03928 then B = BsRGB/12.92 else B = ((BsRGB+0.055)/1.055) ^ 2.4

The way you have created the luminance function it does not take this into account. Which has to do with the conversion of the sRGB space between Gamma and Linear. My sample code:

var luminance: CGFloat {
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0
self.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
let luminance: CGFloat = 0.2126 * inv_gam_sRGB(ic: red) + 0.7152 * inv_gam_sRGB(ic: green) + 0.0722 * inv_gam_sRGB(ic: blue)
let luminance_saturated: CGFloat = luminance <= 0 ? 0 : luminance >= 1 ? 1 : luminance
return luminance_saturated
}

func inv_gam_sRGB(ic:CGFloat) -> CGFloat{
    if ( ic <= 0.03928 ) {
        return ic/12.92
    } else {
        return pow(((ic+0.055)/(1.055)),2.4)
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment