[6a3a178] | 1 | var utils = require('../utils')
|
---|
| 2 | , nodes = require('../nodes')
|
---|
| 3 | , blend = require('./blend')
|
---|
| 4 | , luminosity = require('./luminosity');
|
---|
| 5 |
|
---|
| 6 | /**
|
---|
| 7 | * Returns the contrast ratio object between `top` and `bottom` colors,
|
---|
| 8 | * based on http://leaverou.github.io/contrast-ratio/
|
---|
| 9 | * and https://github.com/LeaVerou/contrast-ratio/blob/gh-pages/color.js#L108
|
---|
| 10 | *
|
---|
| 11 | * Examples:
|
---|
| 12 | *
|
---|
| 13 | * contrast(#000, #fff).ratio
|
---|
| 14 | * => 21
|
---|
| 15 | *
|
---|
| 16 | * contrast(#000, rgba(#FFF, 0.5))
|
---|
| 17 | * => { "ratio": "13.15;", "error": "7.85", "min": "5.3", "max": "21" }
|
---|
| 18 | *
|
---|
| 19 | * @param {RGBA|HSLA} top
|
---|
| 20 | * @param {RGBA|HSLA} [bottom=#fff]
|
---|
| 21 | * @return {Object}
|
---|
| 22 | * @api public
|
---|
| 23 | */
|
---|
| 24 |
|
---|
| 25 | function contrast(top, bottom){
|
---|
| 26 | if ('rgba' != top.nodeName && 'hsla' != top.nodeName) {
|
---|
| 27 | return new nodes.Literal('contrast(' + (top.isNull ? '' : top.toString()) + ')');
|
---|
| 28 | }
|
---|
| 29 | var result = new nodes.Object();
|
---|
| 30 | top = top.rgba;
|
---|
| 31 | bottom = bottom || new nodes.RGBA(255, 255, 255, 1);
|
---|
| 32 | utils.assertColor(bottom);
|
---|
| 33 | bottom = bottom.rgba;
|
---|
| 34 | function contrast(top, bottom) {
|
---|
| 35 | if (1 > top.a) {
|
---|
| 36 | top = blend(top, bottom);
|
---|
| 37 | }
|
---|
| 38 | var l1 = luminosity(bottom).val + 0.05
|
---|
| 39 | , l2 = luminosity(top).val + 0.05
|
---|
| 40 | , ratio = l1 / l2;
|
---|
| 41 |
|
---|
| 42 | if (l2 > l1) {
|
---|
| 43 | ratio = 1 / ratio;
|
---|
| 44 | }
|
---|
| 45 | return Math.round(ratio * 10) / 10;
|
---|
| 46 | }
|
---|
| 47 |
|
---|
| 48 | if (1 <= bottom.a) {
|
---|
| 49 | var resultRatio = new nodes.Unit(contrast(top, bottom));
|
---|
| 50 | result.set('ratio', resultRatio);
|
---|
| 51 | result.set('error', new nodes.Unit(0));
|
---|
| 52 | result.set('min', resultRatio);
|
---|
| 53 | result.set('max', resultRatio);
|
---|
| 54 | } else {
|
---|
| 55 | var onBlack = contrast(top, blend(bottom, new nodes.RGBA(0, 0, 0, 1)))
|
---|
| 56 | , onWhite = contrast(top, blend(bottom, new nodes.RGBA(255, 255, 255, 1)))
|
---|
| 57 | , max = Math.max(onBlack, onWhite);
|
---|
| 58 | function processChannel(topChannel, bottomChannel) {
|
---|
| 59 | return Math.min(Math.max(0, (topChannel - bottomChannel * bottom.a) / (1 - bottom.a)), 255);
|
---|
| 60 | }
|
---|
| 61 | var closest = new nodes.RGBA(
|
---|
| 62 | processChannel(top.r, bottom.r),
|
---|
| 63 | processChannel(top.g, bottom.g),
|
---|
| 64 | processChannel(top.b, bottom.b),
|
---|
| 65 | 1
|
---|
| 66 | );
|
---|
| 67 | var min = contrast(top, blend(bottom, closest));
|
---|
| 68 |
|
---|
| 69 | result.set('ratio', new nodes.Unit(Math.round((min + max) * 50) / 100));
|
---|
| 70 | result.set('error', new nodes.Unit(Math.round((max - min) * 50) / 100));
|
---|
| 71 | result.set('min', new nodes.Unit(min));
|
---|
| 72 | result.set('max', new nodes.Unit(max));
|
---|
| 73 | }
|
---|
| 74 | return result;
|
---|
| 75 | }
|
---|
| 76 | contrast.params = ['top', 'bottom'];
|
---|
| 77 | module.exports = contrast;
|
---|