Skip to content

Instantly share code, notes, and snippets.

@ngquerol
Created September 7, 2016 08:23
Show Gist options
  • Save ngquerol/23d6d5ebd051e18682badafa37e48442 to your computer and use it in GitHub Desktop.
Save ngquerol/23d6d5ebd051e18682badafa37e48442 to your computer and use it in GitHub Desktop.
/// Additional colors accessibility metrics utilities
import Cocoa
import simd
extension NSColor {
/// Returns in an ordered array the following components from this color, in the sRGB color space:
/// - red
/// - green
/// - blue
/// - alpha
/// - note: Each component's value range is 0.0-1.0.
public var rgbComponents: [CGFloat] {
var red: CGFloat = 0.0,
green: CGFloat = 0.0,
blue: CGFloat = 0.0,
alpha: CGFloat = 0.0
if colorSpaceName != NSCalibratedRGBColorSpace {
usingColorSpace(.sRGB)!.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
} else {
getRed(&red, green: &green, blue: &blue, alpha: &alpha)
}
return [red, green, blue, alpha]
}
}
/// W3C color metrics
extension NSColor {
/// Calculates the perceived brightness of a color, according to the W3C "Techniques For Accessibility Evaluation And Repair Tools" working draft.
/// note: - See https://www.w3.org/TR/AERT#color
public var w3cBrightness: CGFloat {
let rgb: [CGFloat] = rgbComponents.map { $0 * 255 }
return (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000
}
/// Calculates the difference between this color and another, according to the W3C "Techniques For Accessibility Evaluation And Repair Tools" working draft.
/// - parameter color: the other color with which the difference from the present color is calculated.
/// - returns: the difference between the two colors.
/// - note:
/// - To be acceptable, a color difference should be >= 500.
/// - See https://www.w3.org/TR/AERT#color
public func w3cColorDifference(to color: NSColor) -> CGFloat {
let rgb1: [CGFloat] = rgbComponents.map { $0 * 255 },
rgb2: [CGFloat] = color.rgbComponents.map { $0 * 255 }
return zip(rgb1, rgb2).map {
max($0, $1) - min($0, $1)
}.reduce(0, +)
}
/// Calculates the relative brightness of a color, according to the W3C "Web Content Accessibility Guidelines (WCAG) 2.0" Recommendation.
/// - note:
/// - The return value is normalized to 0 for darkest black, and 1 for lightest white.
/// - See https://www.w3.org/TR/WCAG20/
public var w3cRelativeLuminance: CGFloat {
let f = { (component: CGFloat) -> CGFloat in
return component <= 0.03928 ? component / 12.92 : pow((component + 0.055) / 1.055, 2.4)
},
rgb: [CGFloat] = rgbComponents.map { f($0) }
return rgb[0] * 0.2126 + rgb[1] * 0.7152 + rgb[2] * 0.0722
}
/// Calculates the contrast ratio of this color and another one, according to the W3C "Web Content Accessibility Guidelines (WCAG) 2.0" Recommendation.
/// - parameter color: the other color with which the contrast ratio relative to this color is calculated.
/// - returns: the contrast ratio of the two colors.
/// - note:
/// - Contrast ratios can range from 1:1 to 21:1.
/// - A contrast ratio should ideally range from 7:1 (best), 4.5:1 (acceptable), to 3:1 (minimum)
/// - See https://www.w3.org/TR/WCAG20/
public func w3cContrastRatio(to color: NSColor) -> CGFloat {
let luminance1 = w3cRelativeLuminance,
luminance2 = color.w3cRelativeLuminance
if luminance1 < luminance2 {
return (luminance2 + 0.05) / (luminance1 + 0.05)
} else {
return (luminance1 + 0.05) / (luminance2 + 0.05)
}
}
}
/// CIE color metrics
extension NSColor {
/// Returns a vector containing the components of this color in the CIE XYZ (CIE 1931) color space.
/// - note: https://en.wikipedia.org/wiki/CIE_1931_color_space#Construction_of_the_CIE_XYZ_color_space_from_the_Wright.E2.80.93Guild_data
public var xyzComponents: [CGFloat] {
let rgb = rgbComponents,
sRGBtoXYZMatrix: matrix_float3x3 = matrix_from_rows(
vector3(0.4124, 0.3576, 0.1805),
vector3(0.2126, 0.7152, 0.0722),
vector3(0.0193, 0.1192, 0.9505)
),
rgbVector: vector_float3 = vector3(Float(rgb[0]), Float(rgb[1]), Float(rgb[2])),
xyzVector: vector_float3 = matrix_multiply(sRGBtoXYZMatrix, rgbVector)
return [CGFloat(xyzVector.x), CGFloat(xyzVector.y), CGFloat(xyzVector.z)]
}
/// Returns a vector containing the components of this color in the CIE-L*a*b* color space:
/// - note: - http://www.easyrgb.com/index.php?X=MATH&H=15#text15
/// - https://en.wikipedia.org/wiki/Lab_color_space#CIELAB-CIEXYZ_conversions
public var labComponents: [CGFloat] {
let tristimulus: [CGFloat] = [95.047, 100, 108.883], // D65 illuminant, 2° observer (CIE 1931)
xyz = xyzComponents.map {
$0 * 100
}, f = { (t: CGFloat) -> CGFloat in
if t > pow(6 / 29, 3) {
return pow(t, 1 / 3)
} else {
return ((1 / 3 as CGFloat) * pow((29 / 6 as CGFloat), 2) * t) + (4 / 29 as CGFloat)
}
}
let fx = f(xyz[0] / tristimulus[0]),
fy = f(xyz[1] / tristimulus[1]),
fz = f(xyz[2] / tristimulus[2])
let l: CGFloat = (116 * fy) - 16,
a: CGFloat = 500 * (fx - fy),
b: CGFloat = 200 * (fy - fz)
return [l, a, b]
}
/// Calculates the ΔE, that is the perceived difference between this color and another one, according to the CIE 76 formula.
/// - note: two colors are deemed "just noticeably different" when the ΔE is <= 2.3.
/// - parameter color: the other color with which the difference from the present color is calculated.
/// - returns: the ΔE of the two colors.
/// - warning: this formula, albeit fast & simple, is known to be problematic when comparing highly saturated colors (these tend to be rated too highly)
public func deltaECIE76(to color: NSColor) -> CGFloat {
let lab1 = labComponents,
lab2 = color.labComponents,
Δl = pow((lab2[0] - lab1[0]), 2),
Δa = pow((lab2[1] - lab1[1]), 2),
Δb = pow((lab2[2] - lab1[2]), 2)
return sqrt(Δl + Δa + Δb)
}
public enum CIE94Application {
case GraphicArts, Textiles
var kL: CGFloat {
switch self {
case .GraphicArts: return 1.0
case .Textiles: return 2.0
}
}
var k1: CGFloat {
switch self {
case .GraphicArts: return 0.045
case .Textiles: return 0.048
}
}
var k2: CGFloat {
switch self {
case .GraphicArts: return 0.015
case .Textiles: return 0.014
}
}
}
/// Calculates the ΔE, that is the perceived difference between this color and another one, according to the CIE 94 formula.
/// - parameter color: the other color with which the difference from the present color is calculated.
/// - parameter application: the application to consider when comparing the two colors.
/// - returns: the ΔE of the two colors.
public func deltaECIE94(to color: NSColor, for constants: CIE94Application = .GraphicArts) -> CGFloat {
let lab1: [CGFloat] = labComponents,
lab2: [CGFloat] = color.labComponents,
ΔL: CGFloat = (lab1[0] - lab2[0]),
ΔA: CGFloat = lab1[1] - lab2[1],
ΔB: CGFloat = lab1[2] - lab2[2],
c1: CGFloat = sqrt(pow(lab1[1], 2) + pow(lab1[2], 2)),
c2: CGFloat = sqrt(pow(lab2[1], 2) + pow(lab2[2], 2)),
ΔC: CGFloat = c1 - c2,
ΔH: CGFloat = pow(ΔA, 2) + pow(ΔB, 2) - pow(ΔC, 2),
kC: CGFloat = 1.0,
kH: CGFloat = 1.0,
sL: CGFloat = 1.0,
sC: CGFloat = 1.0 + (constants.k1 * c1),
sH: CGFloat = 1.0 + (constants.k2 * c1),
ΔLklsl: CGFloat = pow(ΔL / (constants.kL * sL), 2),
ΔCkcsc: CGFloat = pow(ΔC / (kC * sC), 2),
ΔHkhsh: CGFloat = pow(ΔH < 0 ? 0 : sqrt(ΔH) / (kH * sH), 2),
result: CGFloat = ΔLklsl + ΔCkcsc + ΔHkhsh
return result < 0 ? 0 : sqrt(result)
}
/// Calculates the ΔE, that is the perceived difference between this color and another one, according to the CIE 2000 formula.
/// - parameter color: the other color with which the difference from the present color is calculated.
/// - returns: the ΔE of the two colors.
public func deltaECIE2000(to color: NSColor) -> CGFloat {
let lab1: [CGFloat] = labComponents,
lab2: [CGFloat] = color.labComponents
return 0.0
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment