You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

234 lines
5.2 KiB

4 years ago
  1. /* MIT license */
  2. var colorNames = require('color-name');
  3. var swizzle = require('simple-swizzle');
  4. var reverseNames = {};
  5. // create a list of reverse color names
  6. for (var name in colorNames) {
  7. if (colorNames.hasOwnProperty(name)) {
  8. reverseNames[colorNames[name]] = name;
  9. }
  10. }
  11. var cs = module.exports = {
  12. to: {},
  13. get: {}
  14. };
  15. cs.get = function (string) {
  16. var prefix = string.substring(0, 3).toLowerCase();
  17. var val;
  18. var model;
  19. switch (prefix) {
  20. case 'hsl':
  21. val = cs.get.hsl(string);
  22. model = 'hsl';
  23. break;
  24. case 'hwb':
  25. val = cs.get.hwb(string);
  26. model = 'hwb';
  27. break;
  28. default:
  29. val = cs.get.rgb(string);
  30. model = 'rgb';
  31. break;
  32. }
  33. if (!val) {
  34. return null;
  35. }
  36. return {model: model, value: val};
  37. };
  38. cs.get.rgb = function (string) {
  39. if (!string) {
  40. return null;
  41. }
  42. var abbr = /^#([a-f0-9]{3,4})$/i;
  43. var hex = /^#([a-f0-9]{6})([a-f0-9]{2})?$/i;
  44. var rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/;
  45. var per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/;
  46. var keyword = /(\D+)/;
  47. var rgb = [0, 0, 0, 1];
  48. var match;
  49. var i;
  50. var hexAlpha;
  51. if (match = string.match(hex)) {
  52. hexAlpha = match[2];
  53. match = match[1];
  54. for (i = 0; i < 3; i++) {
  55. // https://jsperf.com/slice-vs-substr-vs-substring-methods-long-string/19
  56. var i2 = i * 2;
  57. rgb[i] = parseInt(match.slice(i2, i2 + 2), 16);
  58. }
  59. if (hexAlpha) {
  60. rgb[3] = Math.round((parseInt(hexAlpha, 16) / 255) * 100) / 100;
  61. }
  62. } else if (match = string.match(abbr)) {
  63. match = match[1];
  64. hexAlpha = match[3];
  65. for (i = 0; i < 3; i++) {
  66. rgb[i] = parseInt(match[i] + match[i], 16);
  67. }
  68. if (hexAlpha) {
  69. rgb[3] = Math.round((parseInt(hexAlpha + hexAlpha, 16) / 255) * 100) / 100;
  70. }
  71. } else if (match = string.match(rgba)) {
  72. for (i = 0; i < 3; i++) {
  73. rgb[i] = parseInt(match[i + 1], 0);
  74. }
  75. if (match[4]) {
  76. rgb[3] = parseFloat(match[4]);
  77. }
  78. } else if (match = string.match(per)) {
  79. for (i = 0; i < 3; i++) {
  80. rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55);
  81. }
  82. if (match[4]) {
  83. rgb[3] = parseFloat(match[4]);
  84. }
  85. } else if (match = string.match(keyword)) {
  86. if (match[1] === 'transparent') {
  87. return [0, 0, 0, 0];
  88. }
  89. rgb = colorNames[match[1]];
  90. if (!rgb) {
  91. return null;
  92. }
  93. rgb[3] = 1;
  94. return rgb;
  95. } else {
  96. return null;
  97. }
  98. for (i = 0; i < 3; i++) {
  99. rgb[i] = clamp(rgb[i], 0, 255);
  100. }
  101. rgb[3] = clamp(rgb[3], 0, 1);
  102. return rgb;
  103. };
  104. cs.get.hsl = function (string) {
  105. if (!string) {
  106. return null;
  107. }
  108. var hsl = /^hsla?\(\s*([+-]?(?:\d*\.)?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/;
  109. var match = string.match(hsl);
  110. if (match) {
  111. var alpha = parseFloat(match[4]);
  112. var h = (parseFloat(match[1]) + 360) % 360;
  113. var s = clamp(parseFloat(match[2]), 0, 100);
  114. var l = clamp(parseFloat(match[3]), 0, 100);
  115. var a = clamp(isNaN(alpha) ? 1 : alpha, 0, 1);
  116. return [h, s, l, a];
  117. }
  118. return null;
  119. };
  120. cs.get.hwb = function (string) {
  121. if (!string) {
  122. return null;
  123. }
  124. var hwb = /^hwb\(\s*([+-]?\d*[\.]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/;
  125. var match = string.match(hwb);
  126. if (match) {
  127. var alpha = parseFloat(match[4]);
  128. var h = ((parseFloat(match[1]) % 360) + 360) % 360;
  129. var w = clamp(parseFloat(match[2]), 0, 100);
  130. var b = clamp(parseFloat(match[3]), 0, 100);
  131. var a = clamp(isNaN(alpha) ? 1 : alpha, 0, 1);
  132. return [h, w, b, a];
  133. }
  134. return null;
  135. };
  136. cs.to.hex = function () {
  137. var rgba = swizzle(arguments);
  138. return (
  139. '#' +
  140. hexDouble(rgba[0]) +
  141. hexDouble(rgba[1]) +
  142. hexDouble(rgba[2]) +
  143. (rgba[3] < 1
  144. ? (hexDouble(Math.round(rgba[3] * 255)))
  145. : '')
  146. );
  147. };
  148. cs.to.rgb = function () {
  149. var rgba = swizzle(arguments);
  150. return rgba.length < 4 || rgba[3] === 1
  151. ? 'rgb(' + Math.round(rgba[0]) + ', ' + Math.round(rgba[1]) + ', ' + Math.round(rgba[2]) + ')'
  152. : 'rgba(' + Math.round(rgba[0]) + ', ' + Math.round(rgba[1]) + ', ' + Math.round(rgba[2]) + ', ' + rgba[3] + ')';
  153. };
  154. cs.to.rgb.percent = function () {
  155. var rgba = swizzle(arguments);
  156. var r = Math.round(rgba[0] / 255 * 100);
  157. var g = Math.round(rgba[1] / 255 * 100);
  158. var b = Math.round(rgba[2] / 255 * 100);
  159. return rgba.length < 4 || rgba[3] === 1
  160. ? 'rgb(' + r + '%, ' + g + '%, ' + b + '%)'
  161. : 'rgba(' + r + '%, ' + g + '%, ' + b + '%, ' + rgba[3] + ')';
  162. };
  163. cs.to.hsl = function () {
  164. var hsla = swizzle(arguments);
  165. return hsla.length < 4 || hsla[3] === 1
  166. ? 'hsl(' + hsla[0] + ', ' + hsla[1] + '%, ' + hsla[2] + '%)'
  167. : 'hsla(' + hsla[0] + ', ' + hsla[1] + '%, ' + hsla[2] + '%, ' + hsla[3] + ')';
  168. };
  169. // hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax
  170. // (hwb have alpha optional & 1 is default value)
  171. cs.to.hwb = function () {
  172. var hwba = swizzle(arguments);
  173. var a = '';
  174. if (hwba.length >= 4 && hwba[3] !== 1) {
  175. a = ', ' + hwba[3];
  176. }
  177. return 'hwb(' + hwba[0] + ', ' + hwba[1] + '%, ' + hwba[2] + '%' + a + ')';
  178. };
  179. cs.to.keyword = function (rgb) {
  180. return reverseNames[rgb.slice(0, 3)];
  181. };
  182. // helpers
  183. function clamp(num, min, max) {
  184. return Math.min(Math.max(min, num), max);
  185. }
  186. function hexDouble(num) {
  187. var str = num.toString(16).toUpperCase();
  188. return (str.length < 2) ? '0' + str : str;
  189. }