57bfdd6e858311146ed41b69157331a2c488efb2912f98b99ff1c81c65e729b011a2f06877dce40f15199fb8630d91404c771e0cd6e38325e398c177d0271e 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. function roundFloat(number, decimalPoints) {
  6. const decimal = Math.pow(10, decimalPoints);
  7. return Math.round(number * decimal) / decimal;
  8. }
  9. export class RGBA {
  10. constructor(r, g, b, a = 1) {
  11. this._rgbaBrand = undefined;
  12. this.r = Math.min(255, Math.max(0, r)) | 0;
  13. this.g = Math.min(255, Math.max(0, g)) | 0;
  14. this.b = Math.min(255, Math.max(0, b)) | 0;
  15. this.a = roundFloat(Math.max(Math.min(1, a), 0), 3);
  16. }
  17. static equals(a, b) {
  18. return a.r === b.r && a.g === b.g && a.b === b.b && a.a === b.a;
  19. }
  20. }
  21. export class HSLA {
  22. constructor(h, s, l, a) {
  23. this._hslaBrand = undefined;
  24. this.h = Math.max(Math.min(360, h), 0) | 0;
  25. this.s = roundFloat(Math.max(Math.min(1, s), 0), 3);
  26. this.l = roundFloat(Math.max(Math.min(1, l), 0), 3);
  27. this.a = roundFloat(Math.max(Math.min(1, a), 0), 3);
  28. }
  29. static equals(a, b) {
  30. return a.h === b.h && a.s === b.s && a.l === b.l && a.a === b.a;
  31. }
  32. /**
  33. * Converts an RGB color value to HSL. Conversion formula
  34. * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
  35. * Assumes r, g, and b are contained in the set [0, 255] and
  36. * returns h in the set [0, 360], s, and l in the set [0, 1].
  37. */
  38. static fromRGBA(rgba) {
  39. const r = rgba.r / 255;
  40. const g = rgba.g / 255;
  41. const b = rgba.b / 255;
  42. const a = rgba.a;
  43. const max = Math.max(r, g, b);
  44. const min = Math.min(r, g, b);
  45. let h = 0;
  46. let s = 0;
  47. const l = (min + max) / 2;
  48. const chroma = max - min;
  49. if (chroma > 0) {
  50. s = Math.min((l <= 0.5 ? chroma / (2 * l) : chroma / (2 - (2 * l))), 1);
  51. switch (max) {
  52. case r:
  53. h = (g - b) / chroma + (g < b ? 6 : 0);
  54. break;
  55. case g:
  56. h = (b - r) / chroma + 2;
  57. break;
  58. case b:
  59. h = (r - g) / chroma + 4;
  60. break;
  61. }
  62. h *= 60;
  63. h = Math.round(h);
  64. }
  65. return new HSLA(h, s, l, a);
  66. }
  67. static _hue2rgb(p, q, t) {
  68. if (t < 0) {
  69. t += 1;
  70. }
  71. if (t > 1) {
  72. t -= 1;
  73. }
  74. if (t < 1 / 6) {
  75. return p + (q - p) * 6 * t;
  76. }
  77. if (t < 1 / 2) {
  78. return q;
  79. }
  80. if (t < 2 / 3) {
  81. return p + (q - p) * (2 / 3 - t) * 6;
  82. }
  83. return p;
  84. }
  85. /**
  86. * Converts an HSL color value to RGB. Conversion formula
  87. * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
  88. * Assumes h in the set [0, 360] s, and l are contained in the set [0, 1] and
  89. * returns r, g, and b in the set [0, 255].
  90. */
  91. static toRGBA(hsla) {
  92. const h = hsla.h / 360;
  93. const { s, l, a } = hsla;
  94. let r, g, b;
  95. if (s === 0) {
  96. r = g = b = l; // achromatic
  97. }
  98. else {
  99. const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
  100. const p = 2 * l - q;
  101. r = HSLA._hue2rgb(p, q, h + 1 / 3);
  102. g = HSLA._hue2rgb(p, q, h);
  103. b = HSLA._hue2rgb(p, q, h - 1 / 3);
  104. }
  105. return new RGBA(Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), a);
  106. }
  107. }
  108. export class HSVA {
  109. constructor(h, s, v, a) {
  110. this._hsvaBrand = undefined;
  111. this.h = Math.max(Math.min(360, h), 0) | 0;
  112. this.s = roundFloat(Math.max(Math.min(1, s), 0), 3);
  113. this.v = roundFloat(Math.max(Math.min(1, v), 0), 3);
  114. this.a = roundFloat(Math.max(Math.min(1, a), 0), 3);
  115. }
  116. static equals(a, b) {
  117. return a.h === b.h && a.s === b.s && a.v === b.v && a.a === b.a;
  118. }
  119. // from http://www.rapidtables.com/convert/color/rgb-to-hsv.htm
  120. static fromRGBA(rgba) {
  121. const r = rgba.r / 255;
  122. const g = rgba.g / 255;
  123. const b = rgba.b / 255;
  124. const cmax = Math.max(r, g, b);
  125. const cmin = Math.min(r, g, b);
  126. const delta = cmax - cmin;
  127. const s = cmax === 0 ? 0 : (delta / cmax);
  128. let m;
  129. if (delta === 0) {
  130. m = 0;
  131. }
  132. else if (cmax === r) {
  133. m = ((((g - b) / delta) % 6) + 6) % 6;
  134. }
  135. else if (cmax === g) {
  136. m = ((b - r) / delta) + 2;
  137. }
  138. else {
  139. m = ((r - g) / delta) + 4;
  140. }
  141. return new HSVA(Math.round(m * 60), s, cmax, rgba.a);
  142. }
  143. // from http://www.rapidtables.com/convert/color/hsv-to-rgb.htm
  144. static toRGBA(hsva) {
  145. const { h, s, v, a } = hsva;
  146. const c = v * s;
  147. const x = c * (1 - Math.abs((h / 60) % 2 - 1));
  148. const m = v - c;
  149. let [r, g, b] = [0, 0, 0];
  150. if (h < 60) {
  151. r = c;
  152. g = x;
  153. }
  154. else if (h < 120) {
  155. r = x;
  156. g = c;
  157. }
  158. else if (h < 180) {
  159. g = c;
  160. b = x;
  161. }
  162. else if (h < 240) {
  163. g = x;
  164. b = c;
  165. }
  166. else if (h < 300) {
  167. r = x;
  168. b = c;
  169. }
  170. else if (h <= 360) {
  171. r = c;
  172. b = x;
  173. }
  174. r = Math.round((r + m) * 255);
  175. g = Math.round((g + m) * 255);
  176. b = Math.round((b + m) * 255);
  177. return new RGBA(r, g, b, a);
  178. }
  179. }
  180. export class Color {
  181. constructor(arg) {
  182. if (!arg) {
  183. throw new Error('Color needs a value');
  184. }
  185. else if (arg instanceof RGBA) {
  186. this.rgba = arg;
  187. }
  188. else if (arg instanceof HSLA) {
  189. this._hsla = arg;
  190. this.rgba = HSLA.toRGBA(arg);
  191. }
  192. else if (arg instanceof HSVA) {
  193. this._hsva = arg;
  194. this.rgba = HSVA.toRGBA(arg);
  195. }
  196. else {
  197. throw new Error('Invalid color ctor argument');
  198. }
  199. }
  200. static fromHex(hex) {
  201. return Color.Format.CSS.parseHex(hex) || Color.red;
  202. }
  203. get hsla() {
  204. if (this._hsla) {
  205. return this._hsla;
  206. }
  207. else {
  208. return HSLA.fromRGBA(this.rgba);
  209. }
  210. }
  211. get hsva() {
  212. if (this._hsva) {
  213. return this._hsva;
  214. }
  215. return HSVA.fromRGBA(this.rgba);
  216. }
  217. equals(other) {
  218. return !!other && RGBA.equals(this.rgba, other.rgba) && HSLA.equals(this.hsla, other.hsla) && HSVA.equals(this.hsva, other.hsva);
  219. }
  220. /**
  221. * http://www.w3.org/TR/WCAG20/#relativeluminancedef
  222. * Returns the number in the set [0, 1]. O => Darkest Black. 1 => Lightest white.
  223. */
  224. getRelativeLuminance() {
  225. const R = Color._relativeLuminanceForComponent(this.rgba.r);
  226. const G = Color._relativeLuminanceForComponent(this.rgba.g);
  227. const B = Color._relativeLuminanceForComponent(this.rgba.b);
  228. const luminance = 0.2126 * R + 0.7152 * G + 0.0722 * B;
  229. return roundFloat(luminance, 4);
  230. }
  231. static _relativeLuminanceForComponent(color) {
  232. const c = color / 255;
  233. return (c <= 0.03928) ? c / 12.92 : Math.pow(((c + 0.055) / 1.055), 2.4);
  234. }
  235. /**
  236. * http://24ways.org/2010/calculating-color-contrast
  237. * Return 'true' if lighter color otherwise 'false'
  238. */
  239. isLighter() {
  240. const yiq = (this.rgba.r * 299 + this.rgba.g * 587 + this.rgba.b * 114) / 1000;
  241. return yiq >= 128;
  242. }
  243. isLighterThan(another) {
  244. const lum1 = this.getRelativeLuminance();
  245. const lum2 = another.getRelativeLuminance();
  246. return lum1 > lum2;
  247. }
  248. isDarkerThan(another) {
  249. const lum1 = this.getRelativeLuminance();
  250. const lum2 = another.getRelativeLuminance();
  251. return lum1 < lum2;
  252. }
  253. lighten(factor) {
  254. return new Color(new HSLA(this.hsla.h, this.hsla.s, this.hsla.l + this.hsla.l * factor, this.hsla.a));
  255. }
  256. darken(factor) {
  257. return new Color(new HSLA(this.hsla.h, this.hsla.s, this.hsla.l - this.hsla.l * factor, this.hsla.a));
  258. }
  259. transparent(factor) {
  260. const { r, g, b, a } = this.rgba;
  261. return new Color(new RGBA(r, g, b, a * factor));
  262. }
  263. isTransparent() {
  264. return this.rgba.a === 0;
  265. }
  266. isOpaque() {
  267. return this.rgba.a === 1;
  268. }
  269. opposite() {
  270. return new Color(new RGBA(255 - this.rgba.r, 255 - this.rgba.g, 255 - this.rgba.b, this.rgba.a));
  271. }
  272. toString() {
  273. if (!this._toString) {
  274. this._toString = Color.Format.CSS.format(this);
  275. }
  276. return this._toString;
  277. }
  278. static getLighterColor(of, relative, factor) {
  279. if (of.isLighterThan(relative)) {
  280. return of;
  281. }
  282. factor = factor ? factor : 0.5;
  283. const lum1 = of.getRelativeLuminance();
  284. const lum2 = relative.getRelativeLuminance();
  285. factor = factor * (lum2 - lum1) / lum2;
  286. return of.lighten(factor);
  287. }
  288. static getDarkerColor(of, relative, factor) {
  289. if (of.isDarkerThan(relative)) {
  290. return of;
  291. }
  292. factor = factor ? factor : 0.5;
  293. const lum1 = of.getRelativeLuminance();
  294. const lum2 = relative.getRelativeLuminance();
  295. factor = factor * (lum1 - lum2) / lum1;
  296. return of.darken(factor);
  297. }
  298. }
  299. Color.white = new Color(new RGBA(255, 255, 255, 1));
  300. Color.black = new Color(new RGBA(0, 0, 0, 1));
  301. Color.red = new Color(new RGBA(255, 0, 0, 1));
  302. Color.blue = new Color(new RGBA(0, 0, 255, 1));
  303. Color.green = new Color(new RGBA(0, 255, 0, 1));
  304. Color.cyan = new Color(new RGBA(0, 255, 255, 1));
  305. Color.lightgrey = new Color(new RGBA(211, 211, 211, 1));
  306. Color.transparent = new Color(new RGBA(0, 0, 0, 0));
  307. (function (Color) {
  308. let Format;
  309. (function (Format) {
  310. let CSS;
  311. (function (CSS) {
  312. function formatRGB(color) {
  313. if (color.rgba.a === 1) {
  314. return `rgb(${color.rgba.r}, ${color.rgba.g}, ${color.rgba.b})`;
  315. }
  316. return Color.Format.CSS.formatRGBA(color);
  317. }
  318. CSS.formatRGB = formatRGB;
  319. function formatRGBA(color) {
  320. return `rgba(${color.rgba.r}, ${color.rgba.g}, ${color.rgba.b}, ${+(color.rgba.a).toFixed(2)})`;
  321. }
  322. CSS.formatRGBA = formatRGBA;
  323. function formatHSL(color) {
  324. if (color.hsla.a === 1) {
  325. return `hsl(${color.hsla.h}, ${(color.hsla.s * 100).toFixed(2)}%, ${(color.hsla.l * 100).toFixed(2)}%)`;
  326. }
  327. return Color.Format.CSS.formatHSLA(color);
  328. }
  329. CSS.formatHSL = formatHSL;
  330. function formatHSLA(color) {
  331. return `hsla(${color.hsla.h}, ${(color.hsla.s * 100).toFixed(2)}%, ${(color.hsla.l * 100).toFixed(2)}%, ${color.hsla.a.toFixed(2)})`;
  332. }
  333. CSS.formatHSLA = formatHSLA;
  334. function _toTwoDigitHex(n) {
  335. const r = n.toString(16);
  336. return r.length !== 2 ? '0' + r : r;
  337. }
  338. /**
  339. * Formats the color as #RRGGBB
  340. */
  341. function formatHex(color) {
  342. return `#${_toTwoDigitHex(color.rgba.r)}${_toTwoDigitHex(color.rgba.g)}${_toTwoDigitHex(color.rgba.b)}`;
  343. }
  344. CSS.formatHex = formatHex;
  345. /**
  346. * Formats the color as #RRGGBBAA
  347. * If 'compact' is set, colors without transparancy will be printed as #RRGGBB
  348. */
  349. function formatHexA(color, compact = false) {
  350. if (compact && color.rgba.a === 1) {
  351. return Color.Format.CSS.formatHex(color);
  352. }
  353. return `#${_toTwoDigitHex(color.rgba.r)}${_toTwoDigitHex(color.rgba.g)}${_toTwoDigitHex(color.rgba.b)}${_toTwoDigitHex(Math.round(color.rgba.a * 255))}`;
  354. }
  355. CSS.formatHexA = formatHexA;
  356. /**
  357. * The default format will use HEX if opaque and RGBA otherwise.
  358. */
  359. function format(color) {
  360. if (color.isOpaque()) {
  361. return Color.Format.CSS.formatHex(color);
  362. }
  363. return Color.Format.CSS.formatRGBA(color);
  364. }
  365. CSS.format = format;
  366. /**
  367. * Converts an Hex color value to a Color.
  368. * returns r, g, and b are contained in the set [0, 255]
  369. * @param hex string (#RGB, #RGBA, #RRGGBB or #RRGGBBAA).
  370. */
  371. function parseHex(hex) {
  372. const length = hex.length;
  373. if (length === 0) {
  374. // Invalid color
  375. return null;
  376. }
  377. if (hex.charCodeAt(0) !== 35 /* CharCode.Hash */) {
  378. // Does not begin with a #
  379. return null;
  380. }
  381. if (length === 7) {
  382. // #RRGGBB format
  383. const r = 16 * _parseHexDigit(hex.charCodeAt(1)) + _parseHexDigit(hex.charCodeAt(2));
  384. const g = 16 * _parseHexDigit(hex.charCodeAt(3)) + _parseHexDigit(hex.charCodeAt(4));
  385. const b = 16 * _parseHexDigit(hex.charCodeAt(5)) + _parseHexDigit(hex.charCodeAt(6));
  386. return new Color(new RGBA(r, g, b, 1));
  387. }
  388. if (length === 9) {
  389. // #RRGGBBAA format
  390. const r = 16 * _parseHexDigit(hex.charCodeAt(1)) + _parseHexDigit(hex.charCodeAt(2));
  391. const g = 16 * _parseHexDigit(hex.charCodeAt(3)) + _parseHexDigit(hex.charCodeAt(4));
  392. const b = 16 * _parseHexDigit(hex.charCodeAt(5)) + _parseHexDigit(hex.charCodeAt(6));
  393. const a = 16 * _parseHexDigit(hex.charCodeAt(7)) + _parseHexDigit(hex.charCodeAt(8));
  394. return new Color(new RGBA(r, g, b, a / 255));
  395. }
  396. if (length === 4) {
  397. // #RGB format
  398. const r = _parseHexDigit(hex.charCodeAt(1));
  399. const g = _parseHexDigit(hex.charCodeAt(2));
  400. const b = _parseHexDigit(hex.charCodeAt(3));
  401. return new Color(new RGBA(16 * r + r, 16 * g + g, 16 * b + b));
  402. }
  403. if (length === 5) {
  404. // #RGBA format
  405. const r = _parseHexDigit(hex.charCodeAt(1));
  406. const g = _parseHexDigit(hex.charCodeAt(2));
  407. const b = _parseHexDigit(hex.charCodeAt(3));
  408. const a = _parseHexDigit(hex.charCodeAt(4));
  409. return new Color(new RGBA(16 * r + r, 16 * g + g, 16 * b + b, (16 * a + a) / 255));
  410. }
  411. // Invalid color
  412. return null;
  413. }
  414. CSS.parseHex = parseHex;
  415. function _parseHexDigit(charCode) {
  416. switch (charCode) {
  417. case 48 /* CharCode.Digit0 */: return 0;
  418. case 49 /* CharCode.Digit1 */: return 1;
  419. case 50 /* CharCode.Digit2 */: return 2;
  420. case 51 /* CharCode.Digit3 */: return 3;
  421. case 52 /* CharCode.Digit4 */: return 4;
  422. case 53 /* CharCode.Digit5 */: return 5;
  423. case 54 /* CharCode.Digit6 */: return 6;
  424. case 55 /* CharCode.Digit7 */: return 7;
  425. case 56 /* CharCode.Digit8 */: return 8;
  426. case 57 /* CharCode.Digit9 */: return 9;
  427. case 97 /* CharCode.a */: return 10;
  428. case 65 /* CharCode.A */: return 10;
  429. case 98 /* CharCode.b */: return 11;
  430. case 66 /* CharCode.B */: return 11;
  431. case 99 /* CharCode.c */: return 12;
  432. case 67 /* CharCode.C */: return 12;
  433. case 100 /* CharCode.d */: return 13;
  434. case 68 /* CharCode.D */: return 13;
  435. case 101 /* CharCode.e */: return 14;
  436. case 69 /* CharCode.E */: return 14;
  437. case 102 /* CharCode.f */: return 15;
  438. case 70 /* CharCode.F */: return 15;
  439. }
  440. return 0;
  441. }
  442. })(CSS = Format.CSS || (Format.CSS = {}));
  443. })(Format = Color.Format || (Color.Format = {}));
  444. })(Color || (Color = {}));