var utils = require('../utils') , nodes = require('../nodes') , blend = require('./blend') , luminosity = require('./luminosity'); /** * Returns the contrast ratio object between `top` and `bottom` colors, * based on http://leaverou.github.io/contrast-ratio/ * and https://github.com/LeaVerou/contrast-ratio/blob/gh-pages/color.js#L108 * * Examples: * * contrast(#000, #fff).ratio * => 21 * * contrast(#000, rgba(#FFF, 0.5)) * => { "ratio": "13.15;", "error": "7.85", "min": "5.3", "max": "21" } * * @param {RGBA|HSLA} top * @param {RGBA|HSLA} [bottom=#fff] * @return {Object} * @api public */ module.exports = function contrast(top, bottom){ if ('rgba' != top.nodeName && 'hsla' != top.nodeName) { return new nodes.Literal('contrast(' + (top.isNull ? '' : top.toString()) + ')'); } var result = new nodes.Object(); top = top.rgba; bottom = bottom || new nodes.RGBA(255, 255, 255, 1); utils.assertColor(bottom); bottom = bottom.rgba; function contrast(top, bottom) { if (1 > top.a) { top = blend(top, bottom); } var l1 = luminosity(bottom).val + 0.05 , l2 = luminosity(top).val + 0.05 , ratio = l1 / l2; if (l2 > l1) { ratio = 1 / ratio; } return Math.round(ratio * 10) / 10; } if (1 <= bottom.a) { var resultRatio = new nodes.Unit(contrast(top, bottom)); result.set('ratio', resultRatio); result.set('error', new nodes.Unit(0)); result.set('min', resultRatio); result.set('max', resultRatio); } else { var onBlack = contrast(top, blend(bottom, new nodes.RGBA(0, 0, 0, 1))) , onWhite = contrast(top, blend(bottom, new nodes.RGBA(255, 255, 255, 1))) , max = Math.max(onBlack, onWhite); function processChannel(topChannel, bottomChannel) { return Math.min(Math.max(0, (topChannel - bottomChannel * bottom.a) / (1 - bottom.a)), 255); } var closest = new nodes.RGBA( processChannel(top.r, bottom.r), processChannel(top.g, bottom.g), processChannel(top.b, bottom.b), 1 ); var min = contrast(top, blend(bottom, closest)); result.set('ratio', new nodes.Unit(Math.round((min + max) * 50) / 100)); result.set('error', new nodes.Unit(Math.round((max - min) * 50) / 100)); result.set('min', new nodes.Unit(min)); result.set('max', new nodes.Unit(max)); } return result; }