presets.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. from __future__ import annotations
  2. from typing import Iterable
  3. from typing_extensions import Literal
  4. from .color_util import RGB, LightDark
  5. from .constants import GLOBAL_CFG
  6. def remove_duplicates(seq: Iterable) -> list:
  7. """
  8. Remove duplicate items from a sequence while preserving the order
  9. """
  10. seen = set()
  11. seen_add = seen.add
  12. return [x for x in seq if not (x in seen or seen_add(x))]
  13. class ColorProfile:
  14. raw: list[str]
  15. colors: list[RGB]
  16. spacing: Literal['equal', 'weighted'] = 'equal'
  17. def __init__(self, colors: list[str] | list[RGB]):
  18. if isinstance(colors[0], str):
  19. self.raw = colors
  20. self.colors = [RGB.from_hex(c) for c in colors]
  21. else:
  22. self.colors = colors
  23. def with_weights(self, weights: list[int]) -> list[RGB]:
  24. """
  25. Map colors based on weights
  26. :param weights: Weights of each color (weights[i] = how many times color[i] appears)
  27. :return:
  28. """
  29. return [c for i, w in enumerate(weights) for c in [self.colors[i]] * w]
  30. def with_length(self, length: int) -> list[RGB]:
  31. """
  32. Spread to a specific length of text
  33. :param length: Length of text
  34. :return: List of RGBs of the length
  35. """
  36. preset_len = len(self.colors)
  37. center_i = preset_len // 2
  38. # How many copies of each color should be displayed at least?
  39. repeats = length // preset_len
  40. weights = [repeats] * preset_len
  41. # How many extra space left?
  42. extras = length % preset_len
  43. # If there is an even space left, extend the center by one space
  44. if extras % 2 == 1:
  45. extras -= 1
  46. weights[center_i] += 1
  47. # Add weight to border until there's no space left (extras must be even at this point)
  48. border_i = 0
  49. while extras > 0:
  50. extras -= 2
  51. weights[border_i] += 1
  52. weights[-(border_i + 1)] += 1
  53. border_i += 1
  54. return self.with_weights(weights)
  55. def color_text(self, txt: str, foreground: bool = True, space_only: bool = False) -> str:
  56. """
  57. Color a text
  58. :param txt: Text
  59. :param foreground: Whether the foreground text show the color or the background block
  60. :param space_only: Whether to only color spaces
  61. :return: Colored text
  62. """
  63. colors = self.with_length(len(txt))
  64. result = ''
  65. for i, t in enumerate(txt):
  66. if space_only and t != ' ':
  67. if i > 0 and txt[i - 1] == ' ':
  68. result += '\033[0m'
  69. result += t
  70. else:
  71. result += colors[i].to_ansi(foreground=foreground) + t
  72. result += '\033[0m'
  73. return result
  74. def lighten(self, multiplier: float) -> ColorProfile:
  75. """
  76. Lighten the color profile by a multiplier
  77. :param multiplier: Multiplier
  78. :return: Lightened color profile (original isn't modified)
  79. """
  80. return ColorProfile([c.lighten(multiplier) for c in self.colors])
  81. def set_light_raw(self, light: float, at_least: bool | None = None, at_most: bool | None = None) -> 'ColorProfile':
  82. """
  83. Set HSL lightness value
  84. :param light: Lightness value (0-1)
  85. :param at_least: Set the lightness to at least this value (no change if greater)
  86. :param at_most: Set the lightness to at most this value (no change if lesser)
  87. :return: New color profile (original isn't modified)
  88. """
  89. return ColorProfile([c.set_light(light, at_least, at_most) for c in self.colors])
  90. def set_light_dl(self, light: float, term: LightDark = GLOBAL_CFG.light_dark()):
  91. """
  92. Set HSL lightness value with respect to dark/light terminals
  93. :param light: Lightness value (0-1)
  94. :param term: Terminal color (can be "dark" or "light")
  95. :return: New color profile (original isn't modified)
  96. """
  97. assert term.lower() in ['light', 'dark']
  98. at_least, at_most = (True, None) if term.lower() == 'dark' else (None, True)
  99. return self.set_light_raw(light, at_least, at_most)
  100. def set_light_dl_def(self, term: LightDark | None = None):
  101. """
  102. Set default lightness with respect to dark/light terminals
  103. :param term: Terminal color (can be "dark" or "light")
  104. :return: New color profile (original isn't modified)
  105. """
  106. return self.set_light_dl(GLOBAL_CFG.default_lightness(term), term)
  107. def unique_colors(self) -> ColorProfile:
  108. """
  109. Create another color profile with only the unique colors
  110. """
  111. return ColorProfile(remove_duplicates(self.colors))
  112. PRESETS: dict[str, ColorProfile] = {
  113. 'rainbow': ColorProfile([
  114. '#E50000',
  115. '#FF8D00',
  116. '#FFEE00',
  117. '#028121',
  118. '#004CFF',
  119. '#770088'
  120. ]),
  121. 'transgender': ColorProfile([
  122. '#55CDFD',
  123. '#F6AAB7',
  124. '#FFFFFF',
  125. '#F6AAB7',
  126. '#55CDFD'
  127. ]),
  128. 'nonbinary': ColorProfile([
  129. '#FCF431',
  130. '#FCFCFC',
  131. '#9D59D2',
  132. '#282828'
  133. ]),
  134. 'agender': ColorProfile([
  135. '#000000',
  136. '#BABABA',
  137. '#FFFFFF',
  138. '#BAF484',
  139. '#FFFFFF',
  140. '#BABABA',
  141. '#000000'
  142. ]),
  143. 'queer': ColorProfile([
  144. '#B57FDD',
  145. '#FFFFFF',
  146. '#49821E'
  147. ]),
  148. 'genderfluid': ColorProfile([
  149. '#FE76A2',
  150. '#FFFFFF',
  151. '#BF12D7',
  152. '#000000',
  153. '#303CBE'
  154. ]),
  155. 'bisexual': ColorProfile([
  156. '#D60270',
  157. '#9B4F96',
  158. '#0038A8'
  159. ]),
  160. 'pansexual': ColorProfile([
  161. '#FF1C8D',
  162. '#FFD700',
  163. '#1AB3FF'
  164. ]),
  165. 'polysexual': ColorProfile([
  166. '#F714BA',
  167. '#01D66A',
  168. '#1594F6',
  169. ]),
  170. # omnisexual sorced from https://www.flagcolorcodes.com/omnisexual
  171. 'omnisexual': ColorProfile([
  172. '#FE9ACE',
  173. '#FF53BF',
  174. '#200044',
  175. '#6760FE',
  176. '#8EA6FF',
  177. ]),
  178. # gay men sourced from https://www.flagcolorcodes.com/gay-men
  179. 'gay-men': ColorProfile([
  180. '#078D70',
  181. '#98E8C1',
  182. '#FFFFFF',
  183. '#7BADE2',
  184. '#3D1A78'
  185. ]),
  186. 'lesbian': ColorProfile([
  187. '#D62800',
  188. '#FF9B56',
  189. '#FFFFFF',
  190. '#D462A6',
  191. '#A40062'
  192. ]),
  193. # abrosexual used colorpicker to source from
  194. # https://fyeahaltpride.tumblr.com/post/151704251345/could-you-guys-possibly-make-an-abrosexual-pride
  195. 'abrosexual': ColorProfile([
  196. '#46D294',
  197. '#A3E9CA',
  198. '#FFFFFF',
  199. '#F78BB3',
  200. '#EE1766',
  201. ]),
  202. 'asexual': ColorProfile([
  203. '#000000',
  204. '#A4A4A4',
  205. '#FFFFFF',
  206. '#810081'
  207. ]),
  208. 'aromantic': ColorProfile([
  209. '#3BA740',
  210. '#A8D47A',
  211. '#FFFFFF',
  212. '#ABABAB',
  213. '#000000'
  214. ]),
  215. # aroace1 sourced from https://flag.library.lgbt/flags/aroace/
  216. 'aroace1': ColorProfile([
  217. '#E28C00',
  218. '#ECCD00',
  219. '#FFFFFF',
  220. '#62AEDC',
  221. '#203856'
  222. ]),
  223. 'aroace2': ColorProfile([
  224. '#000000',
  225. '#810081',
  226. '#A4A4A4',
  227. '#FFFFFF',
  228. '#A8D47A',
  229. '#3BA740'
  230. ]),
  231. 'aroace3': ColorProfile([
  232. '#3BA740',
  233. '#A8D47A',
  234. '#FFFFFF',
  235. '#ABABAB',
  236. '#000000',
  237. '#A4A4A4',
  238. '#FFFFFF',
  239. '#810081'
  240. ]),
  241. # below sourced from https://www.flagcolorcodes.com/flags/pride
  242. # goto f"https://www.flagcolorcodes.com/{preset}" for info
  243. # todo: sane sorting
  244. 'autosexual': ColorProfile([
  245. '#99D9EA',
  246. '#7F7F7F'
  247. ]),
  248. 'intergender': ColorProfile([
  249. # todo: use weighted spacing
  250. '#900DC2',
  251. '#900DC2',
  252. '#FFE54F',
  253. '#900DC2',
  254. '#900DC2',
  255. ]),
  256. 'greygender': ColorProfile([
  257. '#B3B3B3',
  258. '#B3B3B3',
  259. '#FFFFFF',
  260. '#062383',
  261. '#062383',
  262. '#FFFFFF',
  263. '#535353',
  264. '#535353',
  265. ]),
  266. 'akiosexual': ColorProfile([
  267. '#F9485E',
  268. '#FEA06A',
  269. '#FEF44C',
  270. '#FFFFFF',
  271. '#000000',
  272. ]),
  273. # bigender sourced from https://www.flagcolorcodes.com/bigender
  274. 'bigender': ColorProfile([
  275. '#C479A2',
  276. '#EDA5CD',
  277. '#D6C7E8',
  278. '#FFFFFF',
  279. '#D6C7E8',
  280. '#9AC7E8',
  281. '#6D82D1',
  282. ]),
  283. # demigender yellow sourced from https://lgbtqia.fandom.com/f/p/4400000000000041031
  284. # other colors sourced from demiboy and demigirl flags
  285. 'demigender': ColorProfile([
  286. '#7F7F7F',
  287. '#C4C4C4',
  288. '#FBFF75',
  289. '#FFFFFF',
  290. '#FBFF75',
  291. '#C4C4C4',
  292. '#7F7F7F',
  293. ]),
  294. # demiboy sourced from https://www.flagcolorcodes.com/demiboy
  295. 'demiboy': ColorProfile([
  296. '#7F7F7F',
  297. '#C4C4C4',
  298. '#9DD7EA',
  299. '#FFFFFF',
  300. '#9DD7EA',
  301. '#C4C4C4',
  302. '#7F7F7F',
  303. ]),
  304. # demigirl sourced from https://www.flagcolorcodes.com/demigirl
  305. 'demigirl': ColorProfile([
  306. '#7F7F7F',
  307. '#C4C4C4',
  308. '#FDADC8',
  309. '#FFFFFF',
  310. '#FDADC8',
  311. '#C4C4C4',
  312. '#7F7F7F',
  313. ]),
  314. 'transmasculine': ColorProfile([
  315. '#FF8ABD',
  316. '#CDF5FE',
  317. '#9AEBFF',
  318. '#74DFFF',
  319. '#9AEBFF',
  320. '#CDF5FE',
  321. '#FF8ABD',
  322. ]),
  323. # transfeminine used colorpicker to source from https://www.deviantart.com/pride-flags/art/Trans-Woman-Transfeminine-1-543925985
  324. # linked from https://gender.fandom.com/wiki/Transfeminine
  325. 'transfeminine': ColorProfile([
  326. '#73DEFF',
  327. '#FFE2EE',
  328. '#FFB5D6',
  329. '#FF8DC0',
  330. '#FFB5D6',
  331. '#FFE2EE',
  332. '#73DEFF',
  333. ]),
  334. # genderfaun sourced from https://www.flagcolorcodes.com/genderfaun
  335. 'genderfaun': ColorProfile([
  336. '#FCD689',
  337. '#FFF09B',
  338. '#FAF9CD',
  339. '#FFFFFF',
  340. '#8EDED9',
  341. '#8CACDE',
  342. '#9782EC',
  343. ]),
  344. 'demifaun': ColorProfile([
  345. '#7F7F7F',
  346. '#7F7F7F',
  347. '#C6C6C6',
  348. '#C6C6C6',
  349. '#FCC688',
  350. '#FFF19C',
  351. '#FFFFFF',
  352. '#8DE0D5',
  353. '#9682EC',
  354. '#C6C6C6',
  355. '#C6C6C6',
  356. '#7F7F7F',
  357. '#7F7F7F',
  358. ]),
  359. # genderfae sourced from https://www.flagcolorcodes.com/genderfae
  360. 'genderfae': ColorProfile([
  361. '#97C3A5',
  362. '#C3DEAE',
  363. '#F9FACD',
  364. '#FFFFFF',
  365. '#FCA2C4',
  366. '#DB8AE4',
  367. '#A97EDD',
  368. ]),
  369. # demifae used colorpicker to source form https://www.deviantart.com/pride-flags/art/Demifae-870194777
  370. 'demifae': ColorProfile([
  371. '#7F7F7F',
  372. '#7F7F7F',
  373. '#C5C5C5',
  374. '#C5C5C5',
  375. '#97C3A4',
  376. '#C4DEAE',
  377. '#FFFFFF',
  378. '#FCA2C5',
  379. '#AB7EDF',
  380. '#C5C5C5',
  381. '#C5C5C5',
  382. '#7F7F7F',
  383. '#7F7F7F',
  384. ]),
  385. 'neutrois': ColorProfile([
  386. '#FFFFFF',
  387. '#1F9F00',
  388. '#000000'
  389. ]),
  390. 'biromantic1': ColorProfile([
  391. '#8869A5',
  392. '#D8A7D8',
  393. '#FFFFFF',
  394. '#FDB18D',
  395. '#151638',
  396. ]),
  397. 'biromantic2': ColorProfile([
  398. '#740194',
  399. '#AEB1AA',
  400. '#FFFFFF',
  401. '#AEB1AA',
  402. '#740194',
  403. ]),
  404. 'autoromantic': ColorProfile([ # symbol interpreted
  405. '#99D9EA',
  406. '#99D9EA',
  407. '#99D9EA',
  408. '#99D9EA',
  409. '#99D9EA',
  410. '#000000',
  411. '#3DA542',
  412. '#3DA542',
  413. '#000000',
  414. '#7F7F7F',
  415. '#7F7F7F',
  416. '#7F7F7F',
  417. '#7F7F7F',
  418. '#7F7F7F',
  419. ]),
  420. # i didn't expect this one to work. cool!
  421. 'boyflux2': ColorProfile([
  422. '#E48AE4',
  423. '#9A81B4',
  424. '#55BFAB',
  425. '#FFFFFF',
  426. '#A8A8A8',
  427. '#81D5EF',
  428. '#81D5EF',
  429. '#81D5EF',
  430. '#81D5EF',
  431. '#81D5EF',
  432. '#69ABE5',
  433. '#69ABE5',
  434. '#69ABE5',
  435. '#69ABE5',
  436. '#69ABE5',
  437. '#69ABE5',
  438. '#69ABE5',
  439. '#69ABE5',
  440. '#69ABE5',
  441. '#69ABE5',
  442. '#5276D4',
  443. '#5276D4',
  444. '#5276D4',
  445. '#5276D4',
  446. '#5276D4',
  447. '#5276D4',
  448. '#5276D4',
  449. '#5276D4',
  450. '#5276D4',
  451. '#5276D4',
  452. ]),
  453. 'beiyang': ColorProfile([
  454. '#DF1B12',
  455. '#FFC600',
  456. '#01639D',
  457. '#FFFFFF',
  458. '#000000',
  459. ]),
  460. "finsexual": ColorProfile([
  461. "#B18EDF",
  462. "#D7B1E2",
  463. "#F7CDE9",
  464. "#F39FCE",
  465. "#EA7BB3",
  466. ]),
  467. }