contrast.js 2.4 KB

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