Created
October 15, 2019 03:30
-
-
Save rjchatfield/4936b1c5c34492f0c5d3aa22e2b6a3cb to your computer and use it in GitHub Desktop.
UIColour contrast and luminance
This file contains 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
/// Rip off of the JavaScript implementation of: | |
/// https://github.com/LeaVerou/contrast-ratio/blob/gh-pages/color.js | |
extension UIColor { | |
typealias Contrast = (ratio: CGFloat, error: CGFloat, min: CGFloat, max: CGFloat) | |
/// Range: 1 ... 21 | |
func contrast(_ other: UIColor) -> Contrast { | |
var other = other | |
// Formula: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef | |
guard alpha < 1 else { | |
if other.alpha < 1 { | |
other = other.overlayOn(self) | |
} | |
let l1 = self.luminance + 0.05 | |
let l2 = other.luminance + 0.05 | |
var ratio = l1/l2 | |
if l2 > l1 { | |
ratio = 1 / ratio | |
} | |
return ( | |
ratio: ratio, | |
error: 0, | |
min: ratio, | |
max: ratio | |
) | |
} | |
// If we’re here, it means we have a semi-transparent background | |
// The text color may or may not be semi-transparent, but that doesn't matter | |
let onBlack = self.overlayOn(.black) | |
let onWhite = self.overlayOn(.white) | |
let contrastOnBlack = onBlack.contrast(other).ratio | |
let contrastOnWhite = onWhite.contrast(other).ratio | |
let minContrast: CGFloat | |
if onBlack.luminance > other.luminance { | |
minContrast = contrastOnBlack | |
} else if onWhite.luminance < other.luminance { | |
minContrast = contrastOnWhite | |
} else { | |
minContrast = 1 | |
} | |
let maxContrast = Swift.max(contrastOnBlack, contrastOnWhite) | |
return ( | |
ratio: (minContrast + maxContrast) / 2.0, | |
error: (maxContrast - minContrast) / 2.0, | |
min: minContrast, | |
max: maxContrast | |
) | |
} | |
/// Computed property that returns the RGBA components of this UIColor instance. | |
var rgbaComponents: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) { | |
var redRational: CGFloat = 0.0 | |
var greenRational: CGFloat = 0.0 | |
var blueRational: CGFloat = 0.0 | |
var alphaRational: CGFloat = 0.0 | |
getRed(&redRational, green: &greenRational, blue: &blueRational, alpha: &alphaRational) | |
return (redRational, greenRational, blueRational, alphaRational) | |
} | |
/// Range: 0 ... 1 | |
var luminance: CGFloat { | |
func magic(_ v: CGFloat) -> CGFloat { | |
assert(v <= 1.0, "Invalid number") | |
return v <= 0.03928 | |
? v / 12.92 | |
: pow((v + 0.055) / 1.055, 2.4) | |
} | |
let components = rgbaComponents | |
return magic(components.red) * 0.2126 | |
+ magic(components.green) * 0.7152 | |
+ magic(components.blue) * 0.0722 | |
} | |
var alpha: CGFloat { self.cgColor.alpha } | |
func overlayOn(_ other: UIColor) -> UIColor { | |
guard alpha < 1 else { return self } | |
let this = self.rgbaComponents | |
let other = other.rgbaComponents | |
return UIColor( | |
red: (this.red * this.alpha) + (other.red * other.alpha * (1 - this.alpha)), | |
green: (this.green * this.alpha) + (other.green * other.alpha * (1 - this.alpha)), | |
blue: (this.blue * this.alpha) + (other.blue * other.alpha * (1 - this.alpha)), | |
alpha: this.alpha + (other.alpha * (1 - this.alpha)) | |
) | |
} | |
} | |
// Example | |
UIColor(red: 1, green: 1, blue: 1, alpha: 1).contrast(UIColor(red: 1, green: 1, blue: 0, alpha: 1)) // 1.074 for yellow | |
UIColor(red: 1, green: 1, blue: 1, alpha: 1).contrast(UIColor(red: 0, green: 0, blue: 1, alpha: 1)) // 8.592 for blue | |
// Reversed | |
UIColor(red: 1, green: 1, blue: 0, alpha: 1).contrast(UIColor(red: 1, green: 1, blue: 1, alpha: 1)) // 1.074 for yellow | |
UIColor(red: 0, green: 0, blue: 1, alpha: 1).contrast(UIColor(red: 1, green: 1, blue: 1, alpha: 1)) // 8.592 for blue | |
// Alpha | |
UIColor(red: 0, green: 0, blue: 1, alpha: 1).contrast(UIColor(red: 1, green: 1, blue: 1, alpha: 0.5)) // 2.625 for blue |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment