1""" Functions for working with colors """
5from typing
import Tuple, Literal
6from .named_colors
import named_colors
7from .misc
import translate
13 return f
'#{r:02x}{g:02x}{b:02x}'
15def html2rgb(html: str) -> tuple:
16 hex_color = html.lstrip(
'#')
17 rgb = tuple(int(hex_color[i:i+2], 16)
for i
in (0, 2, 4))
22 """ Generate `amt` number of colors evenly spaced around the color wheel
23 with a given saturation
and value
26 return [toHtml(*map(
lambda c: round(c*255), colorsys.hsv_to_rgb(*((offset + ((1/amt) * (i + 1))) % 1.001, s, v))))
for i
in range(amt-1)]
30 """ Gets the `amt` number of colors evenly spaced around the color wheel from the given color
31 `v_bias` and `s_bias` are between 0-1
and offset the colors
34 h, s, v = colorsys.rgb_to_hsv(*map(lambda c: c/255, toRgb(html)))
36 return [toHtml(*map(
lambda c: round(c*255), colorsys.hsv_to_rgb(*((h + ((1/amt) * (i + 1))) % 1.001, (s+s_bias) % 1.001, (v+v_bias) % 1.001))))
for i
in range(amt-1)]
40def complimentary_color(*color, rtn:Literal[
'html',
'rgb',
'rgba',
'opengl',
'hsv',
'hls',
'yiq']=
'rgb'):
41 """ Returns the color opposite it on the color wheel, with the same saturation and value. """
42 h, s, v = parse_color(*color, rtn=
'hsv')
44 return parse_color(map(
lambda i: i*255, hsv_to_rgb((h + .5) % 1.0001, s, v)), rtn=rtn)
47def distinct_color(n: int) -> tuple:
53 r = int(math.sin(angle) * 127 + 128)
54 g = int(math.sin(angle + 2 * math.pi / 3) * 127 + 128)
55 b = int(math.sin(angle + 4 * math.pi / 3) * 127 + 128)
61def parse_color(*args, rtn:Literal[
'html',
'rgb',
'rgba',
'opengl',
'hsv',
'hls',
'yiq']=
'rgb', **kwargs) ->
'type(rtn)':
62 """ One color function to rule them all!
63 Parses a color, however you care to pass it,
and returns it however you like.
66 * Positional parameters
67 * parse_color(255, 255, 255, 255)
68 * parse_color(255, 255, 255)
70 * parse_color((255, 255, 255, 255))
71 * parse_color((255, 255, 255))
72 * Keyword parameters/dict
73 * parse_color(r=255, g=255, b=255, a=255)
74 * parse_color({
'r': 255,
'g': 255,
'b': 255,
'a': 255})
75 * parse_color(red=255, green=255, blue=255, alpha=255)
76 * parse_color({
'red': 255,
'green': 255,
'blue': 255,
'alpha': 255})
77 * OpenGL style colors (between -1
and 1)
78 * parse_color(1., 1., 1.)
79 * parse_color((1., 1., 1.))
81 * parse_color(
'#FFFFFF')
82 * parse_color(
'#ffffff')
84 * parse_color(
'white')
85 *
"Random" distinct colors
87 * Anything that has r, g, b attributes
or red, green, blue attributes (callable
or not)
88 * parse_color(QColor(255, 255, 255))
96 * (255, 255, 255, 255)
106 If `a`
is not provided, but it
is selected to be returned, it defaults to the max (255, usually)
108 NOTE: Don
't pass OpenGL colors as dicts or as keyword arguements. It will interpret them as
111 assert len(args)
or len(kwargs)
118 if isinstance(args[0], str):
120 if args[0].startswith(
'#'):
121 r, g, b = [int(html.lstrip(
'#')[i:i+2], 16)
for i
in (0, 2, 4)]
124 if (r, g, b := named_colors.get(args[0].lower()))
is None:
125 raise TypeError(f
'{args[0]} is not a recognized color name')
128 elif isinstance(args[0], (tuple, list)):
129 if len(args[0])
not in (3, 4):
130 raise TypeError(f
'Incorrect number ({len(args[0])}) of color parameters given. Please give either 3 or 4 parameters')
133 if all([isinstance(i, float)
and i >= -1
and i <= 1
for i
in args[0]]):
134 if len(args[0]) == 3:
135 r, g, b = [translate(i, -1, 1, 0, 255)
for i
in args[0]]
137 r, g, b, a = [translate(i, -1, 1, 0, 255)
for i
in args[0]]
140 if len(args[0]) == 3:
146 elif isinstance(args[0], (int, float))
and len(args) == 1:
147 r, g, b = distinct_color(int(args[0]))
150 elif isinstance(args[0], dict)
and len(args) == 1:
151 kwargs.update(args[0])
154 elif hasattr(args[0],
'red')
and hasattr(args[0],
'green')
and hasattr(args[0],
'blue'):
155 if hasattr(args[0],
'alpha'):
156 if hasattr(args[0].red,
'__call__')
and hasattr(args[0].green,
'__call__')
and hasattr(args[0].blue,
'__call__')
and hasattr(args[0].alpha,
'__call__'):
157 r, g, b, a = r.red(), r.green(), r.blue(), r.alpha()
159 r, g, b, a = r.red, r.green, r.blue, r.alpha
162 if hasattr(args[0].red,
'__call__')
and hasattr(args[0].green,
'__call__')
and hasattr(args[0].blue,
'__call__'):
163 r, g, b = r.red(), r.green(), r.blue()
165 r, g, b = r.red, r.green, r.blue
168 elif hasattr(args[0],
'r')
and hasattr(args[0],
'g')
and hasattr(args[0],
'b'):
169 if hasattr(args[0],
'a'):
170 if hasattr(args[0].r,
'__call__')
and hasattr(args[0].g,
'__call__')
and hasattr(args[0].b,
'__call__')
and hasattr(args[0].a,
'__call__'):
171 r, g, b, a = r.r(), r.g(), r.b(), r.a()
173 r, g, b, a = r.r, r.g, r.b, r.a
175 if hasattr(args[0].r,
'__call__')
and hasattr(args[0].g,
'__call__')
and hasattr(args[0].b,
'__call__'):
176 r, g, b = r.r(), r.g(), r.b()
178 r, g, b = r.r, r.g, r.b
181 elif len(args)
in (3, 4):
182 assert isinstance(args[0], (int, float))
and isinstance(args[1], (int, float))
and isinstance(args[2], (int, float))
185 if all([isinstance(i, float)
and i >= -1
and i <= 1
for i
in args]):
187 r, g, b = [translate(i, -1, 1, 0, 255)
for i
in args]
189 r, g, b, a = [translate(i, -1, 1, 0, 255)
for i
in args]
196 elif len(args)
not in (3, 4):
197 raise TypeError(f
'Incorrect number ({len(args)}) of color parameters given. Please give either 3 or 4 parameters')
200 r = r
if ((_r := (kwargs.get(
'r')
or kwargs.get(
'red')))
is None)
else _r
201 g = g
if ((_g := (kwargs.get(
'g')
or kwargs.get(
'green')))
is None)
else _g
202 b = b
if ((_b := (kwargs.get(
'b')
or kwargs.get(
'blue')))
is None)
else _b
203 a = a
if ((_a := (kwargs.get(
'a')
or kwargs.get(
'alpha')))
is None)
else _a
222 if r
is None or b
is None or g
is None:
224 raise TypeError(f
'Unsure how to interpret the parameters given (or no parameters were given)')
227 r, g, b, a = [int(i)
for i
in (r, g, b, a)]
233 return f
'#{r:02x}{g:02x}{b:02x}'
239 return rgb_to_hsv(*[translate(i, 0, 255, 0, 1)
for i
in (r, g, b)])
241 return rgb_to_hls(*[translate(i, 0, 255, 0, 1)
for i
in (r, g, b)])
243 return rgb_to_yiq(*[translate(i, 0, 255, 0, 1)
for i
in (r, g, b)])
247 return [translate(i, 0, 255, -1, 1)
for i
in (r, g, b, a)]
249 raise TypeError(f
"Invalid return type given. Options are `html`, `rgb`, `rgba`, `opengl`")
251def darken(amt, *args) -> Tuple['r', 'g', 'b']:
252 """ Returns the given color, but darkened. Make amount negative to lighten
253 NOTE: If you pass alpha to this function it will still work, but it
255 NOTE: If you pass OpenGL colors to this, it will work, but `amt` still
256 has to be within 0-255
258 return tuple(i - amt
for i
in parse_color(*args))
260def darken(amt, *args) -> Tuple['r', 'g', 'b']:
261 """ Returns the given color, but lightened. Make amount negative to darken
262 NOTE: If you pass alpha to this function it will still work, but it
264 NOTE: If you pass OpenGL colors to this, it will work, but `amt` still
265 has to be within 0-255
267 return tuple(i + amt
for i
in parse_color(*args))
271 NOTE: If you pass alpha to this function it will still work, but it
274 return tuple(255 - i
for i
in parse_color(*args, rtn, **kwargs))
Tuple[ 'r', 'g', 'b'] darken(amt, *args)
Returns the given color, but darkened.
def generate_colors(amt, s=1, v=1, offset=0)
Generate amt number of colors evenly spaced around the color wheel with a given saturation and value.
def furthest_colors(html, amt=5, v_bias=0, s_bias=0)
Gets the amt number of colors evenly spaced around the color wheel from the given color v_bias and s_...
def complimentary_color(*color, Literal['html', 'rgb', 'rgba', 'opengl', 'hsv', 'hls', 'yiq'] rtn='rgb')
Returns the color opposite it on the color wheel, with the same saturation and value.
Tuple[ 'r', 'g', 'b'] invert_color(*args, **kwargs)
Inverts a color.