color.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. 'use strict';
  2. // https://github.com/imathis/hsl-picker/blob/master/assets/javascripts/modules/color.coffee
  3. const rHex3 = /^#[0-9a-f]{3}$/;
  4. const rHex6 = /^#[0-9a-f]{6}$/;
  5. const rRGB = /^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,?\s*(0?\.?\d+)?\s*\)$/;
  6. const rHSL = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*,?\s*(0?\.?\d+)?\s*\)$/;
  7. // https://www.w3.org/TR/css3-color/#svg-color
  8. const colorNames = {
  9. aliceblue: {r: 240, g: 248, b: 255, a: 1},
  10. antiquewhite: {r: 250, g: 235, b: 215, a: 1},
  11. aqua: {r: 0, g: 255, b: 255, a: 1},
  12. aquamarine: {r: 127, g: 255, b: 212, a: 1},
  13. azure: {r: 240, g: 255, b: 255, a: 1},
  14. beige: {r: 245, g: 245, b: 220, a: 1},
  15. bisque: {r: 255, g: 228, b: 196, a: 1},
  16. black: {r: 0, g: 0, b: 0, a: 1},
  17. blanchedalmond: {r: 255, g: 235, b: 205, a: 1},
  18. blue: {r: 0, g: 0, b: 255, a: 1},
  19. blueviolet: {r: 138, g: 43, b: 226, a: 1},
  20. brown: {r: 165, g: 42, b: 42, a: 1},
  21. burlywood: {r: 222, g: 184, b: 135, a: 1},
  22. cadetblue: {r: 95, g: 158, b: 160, a: 1},
  23. chartreuse: {r: 127, g: 255, b: 0, a: 1},
  24. chocolate: {r: 210, g: 105, b: 30, a: 1},
  25. coral: {r: 255, g: 127, b: 80, a: 1},
  26. cornflowerblue: {r: 100, g: 149, b: 237, a: 1},
  27. cornsilk: {r: 255, g: 248, b: 220, a: 1},
  28. crimson: {r: 220, g: 20, b: 60, a: 1},
  29. cyan: {r: 0, g: 255, b: 255, a: 1},
  30. darkblue: {r: 0, g: 0, b: 139, a: 1},
  31. darkcyan: {r: 0, g: 139, b: 139, a: 1},
  32. darkgoldenrod: {r: 184, g: 134, b: 11, a: 1},
  33. darkgray: {r: 169, g: 169, b: 169, a: 1},
  34. darkgreen: {r: 0, g: 100, b: 0, a: 1},
  35. darkgrey: {r: 169, g: 169, b: 169, a: 1},
  36. darkkhaki: {r: 189, g: 183, b: 107, a: 1},
  37. darkmagenta: {r: 139, g: 0, b: 139, a: 1},
  38. darkolivegreen: {r: 85, g: 107, b: 47, a: 1},
  39. darkorange: {r: 255, g: 140, b: 0, a: 1},
  40. darkorchid: {r: 153, g: 50, b: 204, a: 1},
  41. darkred: {r: 139, g: 0, b: 0, a: 1},
  42. darksalmon: {r: 233, g: 150, b: 122, a: 1},
  43. darkseagreen: {r: 143, g: 188, b: 143, a: 1},
  44. darkslateblue: {r: 72, g: 61, b: 139, a: 1},
  45. darkslategray: {r: 47, g: 79, b: 79, a: 1},
  46. darkslategrey: {r: 47, g: 79, b: 79, a: 1},
  47. darkturquoise: {r: 0, g: 206, b: 209, a: 1},
  48. darkviolet: {r: 148, g: 0, b: 211, a: 1},
  49. deeppink: {r: 255, g: 20, b: 147, a: 1},
  50. deepskyblue: {r: 0, g: 191, b: 255, a: 1},
  51. dimgray: {r: 105, g: 105, b: 105, a: 1},
  52. dimgrey: {r: 105, g: 105, b: 105, a: 1},
  53. dodgerblue: {r: 30, g: 144, b: 255, a: 1},
  54. firebrick: {r: 178, g: 34, b: 34, a: 1},
  55. floralwhite: {r: 255, g: 250, b: 240, a: 1},
  56. forestgreen: {r: 34, g: 139, b: 34, a: 1},
  57. fuchsia: {r: 255, g: 0, b: 255, a: 1},
  58. gainsboro: {r: 220, g: 220, b: 220, a: 1},
  59. ghostwhite: {r: 248, g: 248, b: 255, a: 1},
  60. gold: {r: 255, g: 215, b: 0, a: 1},
  61. goldenrod: {r: 218, g: 165, b: 32, a: 1},
  62. gray: {r: 128, g: 128, b: 128, a: 1},
  63. green: {r: 0, g: 128, b: 0, a: 1},
  64. greenyellow: {r: 173, g: 255, b: 47, a: 1},
  65. grey: {r: 128, g: 128, b: 128, a: 1},
  66. honeydew: {r: 240, g: 255, b: 240, a: 1},
  67. hotpink: {r: 255, g: 105, b: 180, a: 1},
  68. indianred: {r: 205, g: 92, b: 92, a: 1},
  69. indigo: {r: 75, g: 0, b: 130, a: 1},
  70. ivory: {r: 255, g: 255, b: 240, a: 1},
  71. khaki: {r: 240, g: 230, b: 140, a: 1},
  72. lavender: {r: 230, g: 230, b: 250, a: 1},
  73. lavenderblush: {r: 255, g: 240, b: 245, a: 1},
  74. lawngreen: {r: 124, g: 252, b: 0, a: 1},
  75. lemonchiffon: {r: 255, g: 250, b: 205, a: 1},
  76. lightblue: {r: 173, g: 216, b: 230, a: 1},
  77. lightcoral: {r: 240, g: 128, b: 128, a: 1},
  78. lightcyan: {r: 224, g: 255, b: 255, a: 1},
  79. lightgoldenrodyellow: {r: 250, g: 250, b: 210, a: 1},
  80. lightgray: {r: 211, g: 211, b: 211, a: 1},
  81. lightgreen: {r: 144, g: 238, b: 144, a: 1},
  82. lightgrey: {r: 211, g: 211, b: 211, a: 1},
  83. lightpink: {r: 255, g: 182, b: 193, a: 1},
  84. lightsalmon: {r: 255, g: 160, b: 122, a: 1},
  85. lightseagreen: {r: 32, g: 178, b: 170, a: 1},
  86. lightskyblue: {r: 135, g: 206, b: 250, a: 1},
  87. lightslategray: {r: 119, g: 136, b: 153, a: 1},
  88. lightslategrey: {r: 119, g: 136, b: 153, a: 1},
  89. lightsteelblue: {r: 176, g: 196, b: 222, a: 1},
  90. lightyellow: {r: 255, g: 255, b: 224, a: 1},
  91. lime: {r: 0, g: 255, b: 0, a: 1},
  92. limegreen: {r: 50, g: 205, b: 50, a: 1},
  93. linen: {r: 250, g: 240, b: 230, a: 1},
  94. magenta: {r: 255, g: 0, b: 255, a: 1},
  95. maroon: {r: 128, g: 0, b: 0, a: 1},
  96. mediumaquamarine: {r: 102, g: 205, b: 170, a: 1},
  97. mediumblue: {r: 0, g: 0, b: 205, a: 1},
  98. mediumorchid: {r: 186, g: 85, b: 211, a: 1},
  99. mediumpurple: {r: 147, g: 112, b: 219, a: 1},
  100. mediumseagreen: {r: 60, g: 179, b: 113, a: 1},
  101. mediumslateblue: {r: 123, g: 104, b: 238, a: 1},
  102. mediumspringgreen: {r: 0, g: 250, b: 154, a: 1},
  103. mediumturquoise: {r: 72, g: 209, b: 204, a: 1},
  104. mediumvioletred: {r: 199, g: 21, b: 133, a: 1},
  105. midnightblue: {r: 25, g: 25, b: 112, a: 1},
  106. mintcream: {r: 245, g: 255, b: 250, a: 1},
  107. mistyrose: {r: 255, g: 228, b: 225, a: 1},
  108. moccasin: {r: 255, g: 228, b: 181, a: 1},
  109. navajowhite: {r: 255, g: 222, b: 173, a: 1},
  110. navy: {r: 0, g: 0, b: 128, a: 1},
  111. oldlace: {r: 253, g: 245, b: 230, a: 1},
  112. olive: {r: 128, g: 128, b: 0, a: 1},
  113. olivedrab: {r: 107, g: 142, b: 35, a: 1},
  114. orange: {r: 255, g: 165, b: 0, a: 1},
  115. orangered: {r: 255, g: 69, b: 0, a: 1},
  116. orchid: {r: 218, g: 112, b: 214, a: 1},
  117. palegoldenrod: {r: 238, g: 232, b: 170, a: 1},
  118. palegreen: {r: 152, g: 251, b: 152, a: 1},
  119. paleturquoise: {r: 175, g: 238, b: 238, a: 1},
  120. palevioletred: {r: 219, g: 112, b: 147, a: 1},
  121. papayawhip: {r: 255, g: 239, b: 213, a: 1},
  122. peachpuff: {r: 255, g: 218, b: 185, a: 1},
  123. peru: {r: 205, g: 133, b: 63, a: 1},
  124. pink: {r: 255, g: 192, b: 203, a: 1},
  125. plum: {r: 221, g: 160, b: 221, a: 1},
  126. powderblue: {r: 176, g: 224, b: 230, a: 1},
  127. purple: {r: 128, g: 0, b: 128, a: 1},
  128. red: {r: 255, g: 0, b: 0, a: 1},
  129. rosybrown: {r: 188, g: 143, b: 143, a: 1},
  130. royalblue: {r: 65, g: 105, b: 225, a: 1},
  131. saddlebrown: {r: 139, g: 69, b: 19, a: 1},
  132. salmon: {r: 250, g: 128, b: 114, a: 1},
  133. sandybrown: {r: 244, g: 164, b: 96, a: 1},
  134. seagreen: {r: 46, g: 139, b: 87, a: 1},
  135. seashell: {r: 255, g: 245, b: 238, a: 1},
  136. sienna: {r: 160, g: 82, b: 45, a: 1},
  137. silver: {r: 192, g: 192, b: 192, a: 1},
  138. skyblue: {r: 135, g: 206, b: 235, a: 1},
  139. slateblue: {r: 106, g: 90, b: 205, a: 1},
  140. slategray: {r: 112, g: 128, b: 144, a: 1},
  141. slategrey: {r: 112, g: 128, b: 144, a: 1},
  142. snow: {r: 255, g: 250, b: 250, a: 1},
  143. springgreen: {r: 0, g: 255, b: 127, a: 1},
  144. steelblue: {r: 70, g: 130, b: 180, a: 1},
  145. tan: {r: 210, g: 180, b: 140, a: 1},
  146. teal: {r: 0, g: 128, b: 128, a: 1},
  147. thistle: {r: 216, g: 191, b: 216, a: 1},
  148. tomato: {r: 255, g: 99, b: 71, a: 1},
  149. turquoise: {r: 64, g: 224, b: 208, a: 1},
  150. violet: {r: 238, g: 130, b: 238, a: 1},
  151. wheat: {r: 245, g: 222, b: 179, a: 1},
  152. white: {r: 255, g: 255, b: 255, a: 1},
  153. whitesmoke: {r: 245, g: 245, b: 245, a: 1},
  154. yellow: {r: 255, g: 255, b: 0, a: 1},
  155. yellowgreen: {r: 154, g: 205, b: 50, a: 1}
  156. };
  157. const convertHue = (p, q, h) => {
  158. if (h < 0) h++;
  159. if (h > 1) h--;
  160. let color;
  161. if (h * 6 < 1) {
  162. color = p + ((q - p) * h * 6);
  163. } else if (h * 2 < 1) {
  164. color = q;
  165. } else if (h * 3 < 2) {
  166. color = p + ((q - p) * ((2 / 3) - h) * 6);
  167. } else {
  168. color = p;
  169. }
  170. return Math.round(color * 255);
  171. };
  172. const convertRGB = value => {
  173. const str = value.toString(16);
  174. if (value < 16) return `0${str}`;
  175. return str;
  176. };
  177. const mixValue = (a, b, ratio) => a + ((b - a) * ratio);
  178. class Color {
  179. /**
  180. * @param {string|{ r: number; g: number; b: number; a: number;}} color
  181. */
  182. constructor(color) {
  183. if (typeof color === 'string') {
  184. this._parse(color);
  185. } else if (color != null && typeof color === 'object') {
  186. this.r = color.r | 0;
  187. this.g = color.g | 0;
  188. this.b = color.b | 0;
  189. this.a = +color.a;
  190. } else {
  191. throw new TypeError('color is required!');
  192. }
  193. if (this.r < 0 || this.r > 255
  194. || this.g < 0 || this.g > 255
  195. || this.b < 0 || this.b > 255
  196. || this.a < 0 || this.a > 1) {
  197. throw new RangeError(`{r: ${this.r}, g: ${this.g}, b: ${this.b}, a: ${this.a}} is invalid.`);
  198. }
  199. }
  200. /**
  201. * @param {string} color
  202. */
  203. _parse(color) {
  204. color = color.toLowerCase();
  205. if (Object.prototype.hasOwnProperty.call(colorNames, color)) {
  206. const obj = colorNames[color];
  207. this.r = obj.r;
  208. this.g = obj.g;
  209. this.b = obj.b;
  210. this.a = obj.a;
  211. return;
  212. }
  213. if (rHex3.test(color)) {
  214. const txt = color.substring(1);
  215. const code = parseInt(txt, 16);
  216. this.r = ((code & 0xF00) >> 8) * 17;
  217. this.g = ((code & 0xF0) >> 4) * 17;
  218. this.b = (code & 0xF) * 17;
  219. this.a = 1;
  220. return;
  221. }
  222. if (rHex6.test(color)) {
  223. const txt = color.substring(1);
  224. const code = parseInt(txt, 16);
  225. this.r = (code & 0xFF0000) >> 16;
  226. this.g = (code & 0xFF00) >> 8;
  227. this.b = code & 0xFF;
  228. this.a = 1;
  229. return;
  230. }
  231. let match = color.match(rRGB);
  232. if (match) {
  233. this.r = match[1] | 0;
  234. this.g = match[2] | 0;
  235. this.b = match[3] | 0;
  236. this.a = match[4] ? +match[4] : 1;
  237. return;
  238. }
  239. match = color.match(rHSL);
  240. if (match) {
  241. const h = +match[1] / 360;
  242. const s = +match[2] / 100;
  243. const l = +match[3] / 100;
  244. this.a = match[4] ? +match[4] : 1;
  245. if (!s) {
  246. this.r = l * 255;
  247. this.g = this.r;
  248. this.b = this.r;
  249. }
  250. const q = l < 0.5 ? l * (1 + s) : l + s - (l * s);
  251. const p = (2 * l) - q;
  252. const rt = h + (1 / 3);
  253. const gt = h;
  254. const bt = h - (1 / 3);
  255. this.r = convertHue(p, q, rt);
  256. this.g = convertHue(p, q, gt);
  257. this.b = convertHue(p, q, bt);
  258. return;
  259. }
  260. throw new Error(`${color} is not a supported color format.`);
  261. }
  262. toString() {
  263. if (this.a === 1) {
  264. const r = convertRGB(this.r);
  265. const g = convertRGB(this.g);
  266. const b = convertRGB(this.b);
  267. if (this.r % 17 || this.g % 17 || this.b % 17) {
  268. return `#${r}${g}${b}`;
  269. }
  270. return `#${r[0]}${g[0]}${b[0]}`;
  271. }
  272. return `rgba(${this.r}, ${this.g}, ${this.b}, ${parseFloat(this.a.toFixed(2))})`;
  273. }
  274. /**
  275. * @param {string|{ r: number; g: number; b: number; a: number;}} color
  276. * @param {number} ratio
  277. */
  278. mix(color, ratio) {
  279. if (ratio > 1 || ratio < 0) {
  280. throw new RangeError('Valid numbers is only between 0 and 1.');
  281. }
  282. switch (ratio) {
  283. case 0:
  284. return new Color(this);
  285. case 1:
  286. return new Color(color);
  287. }
  288. return new Color({
  289. r: Math.round(mixValue(this.r, color.r, ratio)),
  290. g: Math.round(mixValue(this.g, color.g, ratio)),
  291. b: Math.round(mixValue(this.b, color.b, ratio)),
  292. a: mixValue(this.a, color.a, ratio)
  293. });
  294. }
  295. }
  296. module.exports = Color;