presets.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. from __future__ import annotations
  2. from typing_extensions import Literal
  3. from .color_util import RGB
  4. class ColorProfile:
  5. raw: list[str]
  6. colors: list[RGB]
  7. spacing: Literal['equal', 'weighted'] = 'equal'
  8. def __init__(self, colors: list[str] | list[RGB]):
  9. if isinstance(colors[0], str):
  10. self.raw = colors
  11. self.colors = [RGB.from_hex(c) for c in colors]
  12. else:
  13. self.colors = colors
  14. def with_weights(self, weights: list[int]) -> list[RGB]:
  15. """
  16. Map colors based on weights
  17. :param weights: Weights of each color (weights[i] = how many times color[i] appears)
  18. :return:
  19. """
  20. return [c for i, w in enumerate(weights) for c in [self.colors[i]] * w]
  21. def with_length(self, length: int) -> list[RGB]:
  22. """
  23. Spread to a specific length of text
  24. :param length: Length of text
  25. :return: List of RGBs of the length
  26. """
  27. preset_len = len(self.colors)
  28. center_i = preset_len // 2
  29. # How many copies of each color should be displayed at least?
  30. repeats = length // preset_len
  31. weights = [repeats] * preset_len
  32. # How many extra space left?
  33. extras = length % preset_len
  34. # If there is an even space left, extend the center by one space
  35. if extras % 2 == 1:
  36. extras -= 1
  37. weights[center_i] += 1
  38. # Add weight to border until there's no space left (extras must be even at this point)
  39. border_i = 0
  40. while extras > 0:
  41. extras -= 2
  42. weights[border_i] += 1
  43. weights[-(border_i + 1)] += 1
  44. border_i += 1
  45. return self.with_weights(weights)
  46. def color_text(self, txt: str, foreground: bool = True, space_only: bool = False) -> str:
  47. """
  48. Color a text
  49. :param txt: Text
  50. :param foreground: Whether the foreground text show the color or the background block
  51. :param space_only: Whether to only color spaces
  52. :return: Colored text
  53. """
  54. colors = self.with_length(len(txt))
  55. result = ''
  56. for i, t in enumerate(txt):
  57. if space_only and t != ' ':
  58. if i > 0 and txt[i - 1] == ' ':
  59. result += '\033[0m'
  60. result += t
  61. else:
  62. result += colors[i].to_ansi(foreground=foreground) + t
  63. result += '\033[0m'
  64. return result
  65. def lighten(self, multiplier: float) -> ColorProfile:
  66. """
  67. Lighten the color profile by a multiplier
  68. :param multiplier: Multiplier
  69. :return: Lightened color profile (original isn't modified)
  70. """
  71. return ColorProfile([c.lighten(multiplier) for c in self.colors])
  72. def set_light(self, light: int):
  73. """
  74. Set HSL lightness value
  75. :param light: Lightness value
  76. :return: New color profile (original isn't modified)
  77. """
  78. return ColorProfile([c.set_light(light) for c in self.colors])
  79. PRESETS: dict[str, ColorProfile] = {
  80. 'rainbow': ColorProfile([
  81. '#E50000',
  82. '#FF8D00',
  83. '#FFEE00',
  84. '#028121',
  85. '#004CFF',
  86. '#770088'
  87. ]),
  88. 'transgender': ColorProfile([
  89. '#55CDFD',
  90. '#F6AAB7',
  91. '#FFFFFF',
  92. '#F6AAB7',
  93. '#55CDFD'
  94. ]),
  95. 'nonbinary': ColorProfile([
  96. '#FCF431',
  97. '#FCFCFC',
  98. '#9D59D2',
  99. '#282828'
  100. ]),
  101. 'agender': ColorProfile([
  102. '#000000',
  103. '#BABABA',
  104. '#FFFFFF',
  105. '#BAF484',
  106. '#FFFFFF',
  107. '#BABABA',
  108. '#000000'
  109. ]),
  110. 'queer': ColorProfile([
  111. '#B57FDD',
  112. '#FFFFFF',
  113. '#49821E'
  114. ]),
  115. 'genderfluid': ColorProfile([
  116. '#FE76A2',
  117. '#FFFFFF',
  118. '#BF12D7',
  119. '#000000',
  120. '#303CBE'
  121. ]),
  122. 'bisexual': ColorProfile([
  123. '#D60270',
  124. '#9B4F96',
  125. '#0038A8'
  126. ]),
  127. 'pansexual': ColorProfile([
  128. '#FF1C8D',
  129. '#FFD700',
  130. '#1AB3FF'
  131. ]),
  132. 'lesbian': ColorProfile([
  133. '#D62800',
  134. '#FF9B56',
  135. '#FFFFFF',
  136. '#D462A6',
  137. '#A40062'
  138. ]),
  139. 'asexual': ColorProfile([
  140. '#000000',
  141. '#A4A4A4',
  142. '#FFFFFF',
  143. '#810081'
  144. ]),
  145. 'aromantic': ColorProfile([
  146. '#3BA740',
  147. '#A8D47A',
  148. '#FFFFFF',
  149. '#ABABAB',
  150. '#000000'
  151. ]),
  152. # below sourced from https://www.flagcolorcodes.com/flags/pride
  153. # goto f"https://www.flagcolorcodes.com/{preset}" for info
  154. # todo: sane sorting
  155. 'autosexual': ColorProfile([
  156. '#99D9EA',
  157. '#7F7F7F'
  158. ]),
  159. 'intergender': ColorProfile([
  160. # todo: use weighted spacing
  161. '#900DC2',
  162. '#900DC2',
  163. '#FFE54F',
  164. '#900DC2',
  165. '#900DC2',
  166. ]),
  167. 'greygender': ColorProfile([
  168. '#B3B3B3',
  169. '#B3B3B3',
  170. '#FFFFFF',
  171. '#062383',
  172. '#062383',
  173. '#FFFFFF',
  174. '#535353',
  175. '#535353',
  176. ]),
  177. 'akiosexual': ColorProfile([
  178. '#F9485E',
  179. '#FEA06A',
  180. '#FEF44C',
  181. '#FFFFFF',
  182. '#000000',
  183. ]),
  184. 'transmasculine': ColorProfile([
  185. '#FF8ABD',
  186. '#CDF5FE',
  187. '#9AEBFF',
  188. '#74DFFF',
  189. '#9AEBFF',
  190. '#CDF5FE',
  191. '#FF8ABD',
  192. ]),
  193. 'demifaun': ColorProfile([
  194. '#7F7F7F',
  195. '#7F7F7F',
  196. '#C6C6C6',
  197. '#C6C6C6',
  198. '#FCC688',
  199. '#FFF19C',
  200. '#FFFFFF',
  201. '#8DE0D5',
  202. '#9682EC',
  203. '#C6C6C6',
  204. '#C6C6C6',
  205. '#7F7F7F',
  206. '#7F7F7F',
  207. ]),
  208. 'neutrois': ColorProfile([
  209. '#FFFFFF',
  210. '#1F9F00',
  211. '#000000'
  212. ]),
  213. 'biromantic1': ColorProfile([
  214. '#8869A5',
  215. '#D8A7D8',
  216. '#FFFFFF',
  217. '#FDB18D',
  218. '#151638',
  219. ]),
  220. 'biromantic2': ColorProfile([
  221. '#740194',
  222. '#AEB1AA',
  223. '#FFFFFF',
  224. '#AEB1AA',
  225. '#740194',
  226. ]),
  227. 'autoromantic': ColorProfile([ # symbol interpreted
  228. '#99D9EA',
  229. '#99D9EA',
  230. '#99D9EA',
  231. '#99D9EA',
  232. '#99D9EA',
  233. '#000000',
  234. '#3DA542',
  235. '#3DA542',
  236. '#000000',
  237. '#7F7F7F',
  238. '#7F7F7F',
  239. '#7F7F7F',
  240. '#7F7F7F',
  241. '#7F7F7F',
  242. ]),
  243. # i didn't expect this one to work. cool!
  244. 'boyflux2': ColorProfile([
  245. '#E48AE4',
  246. '#9A81B4',
  247. '#55BFAB',
  248. '#FFFFFF',
  249. '#A8A8A8',
  250. '#81D5EF',
  251. '#81D5EF',
  252. '#81D5EF',
  253. '#81D5EF',
  254. '#81D5EF',
  255. '#69ABE5',
  256. '#69ABE5',
  257. '#69ABE5',
  258. '#69ABE5',
  259. '#69ABE5',
  260. '#69ABE5',
  261. '#69ABE5',
  262. '#69ABE5',
  263. '#69ABE5',
  264. '#69ABE5',
  265. '#5276D4',
  266. '#5276D4',
  267. '#5276D4',
  268. '#5276D4',
  269. '#5276D4',
  270. '#5276D4',
  271. '#5276D4',
  272. '#5276D4',
  273. '#5276D4',
  274. '#5276D4',
  275. ]),
  276. 'beiyang': ColorProfile([
  277. '#DF1B12',
  278. '#FFC600',
  279. '#01639D',
  280. '#FFFFFF',
  281. '#000000',
  282. ]),
  283. }