gen-color.ts 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. // src/utils/gen-color.ts
  2. // 定义颜色类型
  3. type RGB = {
  4. r: number;
  5. g: number;
  6. b: number;
  7. };
  8. type HSL = {
  9. h: number;
  10. s: number;
  11. l: number;
  12. };
  13. type HEX =
  14. | '0'
  15. | '1'
  16. | '2'
  17. | '3'
  18. | '4'
  19. | '5'
  20. | '6'
  21. | '7'
  22. | '8'
  23. | '9'
  24. | 'A'
  25. | 'B'
  26. | 'C'
  27. | 'D'
  28. | 'E'
  29. | 'F';
  30. const RGBUnit = 255;
  31. const HEX_MAP: Record<HEX, number> = {
  32. 0: 0,
  33. 1: 1,
  34. 2: 2,
  35. 3: 3,
  36. 4: 4,
  37. 5: 5,
  38. 6: 6,
  39. 7: 7,
  40. 8: 8,
  41. 9: 9,
  42. A: 10,
  43. B: 11,
  44. C: 12,
  45. D: 13,
  46. E: 14,
  47. F: 15,
  48. };
  49. const rgbWhite = {
  50. r: 255,
  51. g: 255,
  52. b: 255,
  53. };
  54. const rgbBlack = {
  55. r: 0,
  56. g: 0,
  57. b: 0,
  58. };
  59. /**
  60. * RGB颜色转HSL颜色值
  61. * @param r 红色值
  62. * @param g 绿色值
  63. * @param b 蓝色值
  64. * @returns { h: [0, 360]; s: [0, 1]; l: [0, 1] }
  65. */
  66. function rgbToHsl(rgb: RGB): HSL {
  67. let { r, g, b } = rgb;
  68. const hsl = {
  69. h: 0,
  70. s: 0,
  71. l: 0,
  72. };
  73. // 计算rgb基数 ∈ [0, 1]
  74. r /= RGBUnit;
  75. g /= RGBUnit;
  76. b /= RGBUnit;
  77. const max = Math.max(r, g, b);
  78. const min = Math.min(r, g, b);
  79. // 计算h
  80. if (max === min) {
  81. hsl.h = 0;
  82. } else if (max === r) {
  83. hsl.h = 60 * ((g - b) / (max - min)) + (g >= b ? 0 : 360);
  84. } else if (max === g) {
  85. hsl.h = 60 * ((b - r) / (max - min)) + 120;
  86. } else {
  87. hsl.h = 60 * ((r - g) / (max - min)) + 240;
  88. }
  89. hsl.h = hsl.h > 360 ? hsl.h - 360 : hsl.h;
  90. // 计算l
  91. hsl.l = (max + min) / 2;
  92. // 计算s
  93. if (hsl.l === 0 || max === min) {
  94. // 灰/白/黑
  95. hsl.s = 0;
  96. } else if (hsl.l > 0 && hsl.l <= 0.5) {
  97. hsl.s = (max - min) / (max + min);
  98. } else {
  99. hsl.s = (max - min) / (2 - (max + min));
  100. }
  101. return hsl;
  102. }
  103. /**
  104. * hsl -> rgb
  105. * @param h [0, 360]
  106. * @param s [0, 1]
  107. * @param l [0, 1]
  108. * @returns RGB
  109. */
  110. function hslToRgb(hsl: HSL): RGB {
  111. const { h, s, l } = hsl;
  112. const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
  113. const p = 2 * l - q;
  114. const hUnit = h / 360; // 色相转换为 [0, 1]
  115. const Cr = fillCircleVal(hUnit + 1 / 3);
  116. const Cg = fillCircleVal(hUnit);
  117. const Cb = fillCircleVal(hUnit - 1 / 3);
  118. // 保持 [0, 1] 环状取值
  119. function fillCircleVal(val: number): number {
  120. return val < 0 ? val + 1 : val > 1 ? val - 1 : val;
  121. }
  122. function computedRgb(val: number): number {
  123. let colorVal: number;
  124. if (val < 1 / 6) {
  125. colorVal = p + (q - p) * 6 * val;
  126. } else if (val >= 1 / 6 && val < 1 / 2) {
  127. colorVal = q;
  128. } else if (val >= 1 / 2 && val < 2 / 3) {
  129. colorVal = p + (q - p) * 6 * (2 / 3 - val);
  130. } else {
  131. colorVal = p;
  132. }
  133. return colorVal * 255;
  134. }
  135. return {
  136. r: Number(computedRgb(Cr).toFixed(0)),
  137. g: Number(computedRgb(Cg).toFixed(0)),
  138. b: Number(computedRgb(Cb).toFixed(0)),
  139. };
  140. }
  141. /**
  142. * 16进制颜色转换RGB
  143. * @param color #rrggbb
  144. * @returns RGB
  145. */
  146. function hexToRGB(hex: string): RGB {
  147. hex = hex.toUpperCase();
  148. const hexRegExp = /^#([0-9A-F]{6})$/;
  149. if (!hexRegExp.test(hex)) {
  150. throw new Error('请传入合法的16进制颜色值,eg: #FF0000, input: ' + hex);
  151. }
  152. const hexValArr = (hexRegExp.exec(hex)?.[1] || '000000').split(
  153. ''
  154. ) as Array<HEX>;
  155. return {
  156. r: HEX_MAP[hexValArr[0]] * 16 + HEX_MAP[hexValArr[1]],
  157. g: HEX_MAP[hexValArr[2]] * 16 + HEX_MAP[hexValArr[3]],
  158. b: HEX_MAP[hexValArr[4]] * 16 + HEX_MAP[hexValArr[5]],
  159. };
  160. }
  161. /**
  162. * rgb 转 16进制
  163. * @param rgb RGB
  164. * @returns #HEX{6}
  165. */
  166. function rgbToHex(rgb: RGB): string {
  167. const HEX_MAP_REVERSE: Record<string, HEX> = {};
  168. for (const key in HEX_MAP) {
  169. HEX_MAP_REVERSE[HEX_MAP[key as HEX]] = key as HEX;
  170. }
  171. function getRemainderAndQuotient(val: number): string {
  172. val = Math.round(val);
  173. return `${HEX_MAP_REVERSE[Math.floor(val / 16)]}${
  174. HEX_MAP_REVERSE[val % 16]
  175. }`;
  176. }
  177. return `#${getRemainderAndQuotient(rgb.r)}${getRemainderAndQuotient(
  178. rgb.g
  179. )}${getRemainderAndQuotient(rgb.b)}`;
  180. }
  181. // hsl 转 16进制
  182. function hslToHex(hsl: HSL): string {
  183. return rgbToHex(hslToRgb(hsl));
  184. }
  185. // 16进制 转 hsl
  186. function hexToHsl(hex: string): HSL {
  187. return rgbToHsl(hexToRGB(hex));
  188. }
  189. // 生成混合色(混黑 + 混白)
  190. function genMixColor(base: string | RGB | HSL): {
  191. DEFAULT: string;
  192. dark: {
  193. 1: string;
  194. 2: string;
  195. 3: string;
  196. 4: string;
  197. 5: string;
  198. 6: string;
  199. 7: string;
  200. 8: string;
  201. 9: string;
  202. };
  203. light: {
  204. 1: string;
  205. 2: string;
  206. 3: string;
  207. 4: string;
  208. 5: string;
  209. 6: string;
  210. 7: string;
  211. 8: string;
  212. 9: string;
  213. };
  214. } {
  215. // 基准色统一转换为RGB,方便进行混合色生成
  216. if (typeof base === 'string') {
  217. base = hexToRGB(base);
  218. } else if ('h' in base) {
  219. base = hslToRgb(base);
  220. }
  221. // 混合色(这里参考Sass mix函数逻辑,本质上就是两种颜色的rbg各个值加权平均)
  222. function mix(color: RGB, mixColor: RGB, weight: number): RGB {
  223. return {
  224. r: color.r * (1 - weight) + mixColor.r * weight,
  225. g: color.g * (1 - weight) + mixColor.g * weight,
  226. b: color.b * (1 - weight) + mixColor.b * weight,
  227. };
  228. }
  229. return {
  230. DEFAULT: rgbToHex(base),
  231. dark: {
  232. 1: rgbToHex(mix(base, rgbBlack, 0.1)),
  233. 2: rgbToHex(mix(base, rgbBlack, 0.2)),
  234. 3: rgbToHex(mix(base, rgbBlack, 0.3)),
  235. 4: rgbToHex(mix(base, rgbBlack, 0.4)),
  236. 5: rgbToHex(mix(base, rgbBlack, 0.5)),
  237. 6: rgbToHex(mix(base, rgbBlack, 0.6)),
  238. 7: rgbToHex(mix(base, rgbBlack, 0.7)),
  239. 8: rgbToHex(mix(base, rgbBlack, 0.78)),
  240. 9: rgbToHex(mix(base, rgbBlack, 0.85)),
  241. },
  242. light: {
  243. 1: rgbToHex(mix(base, rgbWhite, 0.1)),
  244. 2: rgbToHex(mix(base, rgbWhite, 0.2)),
  245. 3: rgbToHex(mix(base, rgbWhite, 0.3)),
  246. 4: rgbToHex(mix(base, rgbWhite, 0.4)),
  247. 5: rgbToHex(mix(base, rgbWhite, 0.5)),
  248. 6: rgbToHex(mix(base, rgbWhite, 0.6)),
  249. 7: rgbToHex(mix(base, rgbWhite, 0.7)),
  250. 8: rgbToHex(mix(base, rgbWhite, 0.78)),
  251. 9: rgbToHex(mix(base, rgbWhite, 0.85)),
  252. },
  253. };
  254. }
  255. export {
  256. genMixColor,
  257. rgbToHsl,
  258. rgbToHex,
  259. hslToRgb,
  260. hslToHex,
  261. hexToRGB,
  262. hexToHsl,
  263. };