Created
March 30, 2017 11:46
-
-
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.
This file contains hidden or 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 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 | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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
}