Created
September 7, 2016 08:23
-
-
Save ngquerol/23d6d5ebd051e18682badafa37e48442 to your computer and use it in GitHub Desktop.
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
/// 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