xulbux 1.5.5__py3-none-any.whl → 1.5.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of xulbux might be problematic. Click here for more details.
- xulbux/__init__.py +20 -10
- xulbux/{__help__.py → _cli_.py} +15 -37
- xulbux/_consts_.py +108 -111
- xulbux/xx_cmd.py +245 -104
- xulbux/xx_code.py +28 -25
- xulbux/xx_color.py +330 -182
- xulbux/xx_data.py +214 -90
- xulbux/xx_env_vars.py +36 -23
- xulbux/xx_file.py +20 -14
- xulbux/xx_format_codes.py +154 -88
- xulbux/xx_json.py +36 -16
- xulbux/xx_path.py +38 -23
- xulbux/xx_regex.py +44 -27
- xulbux/xx_string.py +75 -47
- xulbux/xx_system.py +37 -26
- {xulbux-1.5.5.dist-info → xulbux-1.5.7.dist-info}/METADATA +14 -10
- xulbux-1.5.7.dist-info/RECORD +20 -0
- {xulbux-1.5.5.dist-info → xulbux-1.5.7.dist-info}/WHEEL +1 -1
- xulbux-1.5.7.dist-info/entry_points.txt +3 -0
- xulbux-1.5.5.dist-info/RECORD +0 -20
- xulbux-1.5.5.dist-info/entry_points.txt +0 -2
- {xulbux-1.5.5.dist-info → xulbux-1.5.7.dist-info}/licenses/LICENSE +0 -0
xulbux/xx_color.py
CHANGED
|
@@ -31,14 +31,11 @@ The `Color` class, which contains all sorts of different color-related methods:
|
|
|
31
31
|
- saturation
|
|
32
32
|
"""
|
|
33
33
|
|
|
34
|
-
|
|
35
34
|
from .xx_regex import *
|
|
36
35
|
|
|
37
36
|
import re as _re
|
|
38
37
|
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
42
39
|
class rgba:
|
|
43
40
|
"""An RGB/RGBA color: is a tuple of 3 integers, representing the red (`0`-`255`), green (`0`-`255`), and blue (`0`-`255`).<br>
|
|
44
41
|
Also includes an optional 4th param, which is a float, that represents the alpha channel (`0.0`-`1.0`).\n
|
|
@@ -62,14 +59,18 @@ class rgba:
|
|
|
62
59
|
- `with_alpha(alpha)` to create a new color with different alpha
|
|
63
60
|
- `complementary()` to get the complementary color"""
|
|
64
61
|
|
|
65
|
-
def __init__(self, r:int, g:int, b:int, a:float = None):
|
|
62
|
+
def __init__(self, r: int, g: int, b: int, a: float = None):
|
|
66
63
|
if any(isinstance(x, rgba) for x in (r, g, b)):
|
|
67
|
-
raise ValueError(
|
|
64
|
+
raise ValueError("Color is already a rgba() color")
|
|
68
65
|
elif not all(isinstance(x, int) and 0 <= x <= 255 for x in (r, g, b)):
|
|
69
|
-
raise ValueError(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
raise ValueError(
|
|
67
|
+
"RGBA color must have R G B as integers in [0, 255]: got",
|
|
68
|
+
(r, g, b),
|
|
69
|
+
)
|
|
70
|
+
elif a is not None and not (isinstance(a, (int, float)) and 0 <= a <= 1):
|
|
71
|
+
raise ValueError("Alpha channel must be a float/int in [0.0, 1.0]: got", a)
|
|
72
|
+
self.r, self.g, self.b = r, g, b
|
|
73
|
+
self.a = (1.0 if a > 1.0 else float(a)) if a else None
|
|
73
74
|
|
|
74
75
|
def __len__(self):
|
|
75
76
|
return 4 if self.a else 3
|
|
@@ -77,6 +78,9 @@ class rgba:
|
|
|
77
78
|
def __iter__(self):
|
|
78
79
|
return iter((self.r, self.g, self.b) + ((self.a,) if self.a else ()))
|
|
79
80
|
|
|
81
|
+
def __dict__(self):
|
|
82
|
+
return self.dict()
|
|
83
|
+
|
|
80
84
|
def __getitem__(self, index):
|
|
81
85
|
return ((self.r, self.g, self.b) + ((self.a,) if self.a else ()))[index]
|
|
82
86
|
|
|
@@ -87,16 +91,14 @@ class rgba:
|
|
|
87
91
|
return f'({self.r}, {self.g}, {self.b}{f", {self.a}" if self.a else ""})'
|
|
88
92
|
|
|
89
93
|
def __eq__(self, other):
|
|
90
|
-
if not isinstance(other, rgba):
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
"""Returns the color components as a tuple `(r, g, b)` or `(r, g, b, a)` if alpha is present"""
|
|
99
|
-
return tuple(self.list())
|
|
94
|
+
if not isinstance(other, rgba):
|
|
95
|
+
return False
|
|
96
|
+
return (self.r, self.g, self.b, self.a) == (
|
|
97
|
+
other[0],
|
|
98
|
+
other[1],
|
|
99
|
+
other[2],
|
|
100
|
+
other[3],
|
|
101
|
+
)
|
|
100
102
|
|
|
101
103
|
def dict(self) -> dict:
|
|
102
104
|
"""Returns the color components as a dictionary with keys `'r'`, `'g'`, `'b'` and optionally `'a'`"""
|
|
@@ -106,59 +108,61 @@ class rgba:
|
|
|
106
108
|
"""Returns the color components as separate values `r, g, b, a`"""
|
|
107
109
|
return self.r, self.g, self.b, self.a
|
|
108
110
|
|
|
109
|
-
def to_hsla(self) ->
|
|
111
|
+
def to_hsla(self) -> "hsla":
|
|
110
112
|
"""Returns the color as a `hsla()` color"""
|
|
111
113
|
return hsla(*self._rgb_to_hsl(self.r, self.g, self.b), self.a)
|
|
112
114
|
|
|
113
|
-
def to_hexa(self) ->
|
|
115
|
+
def to_hexa(self) -> "hexa":
|
|
114
116
|
"""Returns the color as a `hexa()` color"""
|
|
115
117
|
return hexa(f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}" if self.a else ""}')
|
|
116
118
|
|
|
117
119
|
def has_alpha(self) -> bool:
|
|
118
120
|
"""Returns `True` if the color has an alpha channel and `False` otherwise"""
|
|
119
|
-
return self.a
|
|
121
|
+
return self.a is not None
|
|
120
122
|
|
|
121
|
-
def lighten(self, amount:float) ->
|
|
123
|
+
def lighten(self, amount: float) -> "rgba":
|
|
122
124
|
"""Increases the colors lightness by the specified amount (`0.0`-`1.0`)"""
|
|
123
125
|
self.r, self.g, self.b, self.a = self.to_hsla().lighten(amount).to_rgba().values()
|
|
124
126
|
return rgba(self.r, self.g, self.b, self.a)
|
|
125
127
|
|
|
126
|
-
def darken(self, amount:float) ->
|
|
128
|
+
def darken(self, amount: float) -> "rgba":
|
|
127
129
|
"""Decreases the colors lightness by the specified amount (`0.0`-`1.0`)"""
|
|
128
130
|
self.r, self.g, self.b, self.a = self.to_hsla().darken(amount).to_rgba().values()
|
|
129
131
|
return rgba(self.r, self.g, self.b, self.a)
|
|
130
132
|
|
|
131
|
-
def saturate(self, amount:float) ->
|
|
133
|
+
def saturate(self, amount: float) -> "rgba":
|
|
132
134
|
"""Increases the colors saturation by the specified amount (`0.0`-`1.0`)"""
|
|
133
135
|
self.r, self.g, self.b, self.a = self.to_hsla().saturate(amount).to_rgba().values()
|
|
134
136
|
return rgba(self.r, self.g, self.b, self.a)
|
|
135
137
|
|
|
136
|
-
def desaturate(self, amount:float) ->
|
|
138
|
+
def desaturate(self, amount: float) -> "rgba":
|
|
137
139
|
"""Decreases the colors saturation by the specified amount (`0.0`-`1.0`)"""
|
|
138
140
|
self.r, self.g, self.b, self.a = self.to_hsla().desaturate(amount).to_rgba().values()
|
|
139
141
|
return rgba(self.r, self.g, self.b, self.a)
|
|
140
142
|
|
|
141
|
-
def rotate(self, degrees:int) ->
|
|
143
|
+
def rotate(self, degrees: int) -> "rgba":
|
|
142
144
|
"""Rotates the colors hue by the specified number of degrees"""
|
|
143
145
|
self.r, self.g, self.b, self.a = self.to_hsla().rotate(degrees).to_rgba().values()
|
|
144
146
|
return rgba(self.r, self.g, self.b, self.a)
|
|
145
147
|
|
|
146
|
-
def invert(self, invert_alpha:bool = False) ->
|
|
148
|
+
def invert(self, invert_alpha: bool = False) -> "rgba":
|
|
147
149
|
"""Inverts the color by rotating hue by 180 degrees and inverting lightness"""
|
|
148
150
|
self.r, self.g, self.b = 255 - self.r, 255 - self.g, 255 - self.b
|
|
149
|
-
if invert_alpha:
|
|
151
|
+
if invert_alpha:
|
|
152
|
+
self.a = 1 - self.a
|
|
150
153
|
return rgba(self.r, self.g, self.b, self.a)
|
|
151
154
|
|
|
152
|
-
def grayscale(self) ->
|
|
155
|
+
def grayscale(self) -> "rgba":
|
|
153
156
|
"""Converts the color to grayscale using the luminance formula"""
|
|
154
157
|
self.r = self.g = self.b = Color.luminance(self.r, self.g, self.b)
|
|
155
158
|
return rgba(self.r, self.g, self.b, self.a)
|
|
156
159
|
|
|
157
|
-
def blend(self, other:
|
|
160
|
+
def blend(self, other: "rgba", ratio: float = 0.5, additive_alpha: bool = False) -> "rgba":
|
|
158
161
|
"""Blends the current color with another color using the specified ratio (`0.0`-`1.0`):<br>
|
|
159
162
|
If `ratio` is `0.0` it means 100% of the current color and 0% of the `other` color (1:0 mixture)<br>
|
|
160
163
|
If `ratio` is `0.5` it means 50% of both colors (1:1 mixture)<br>
|
|
161
|
-
If `ratio` is `1.0` it means 0% of the current color and 100% of the `other` color (0:1 mixture)
|
|
164
|
+
If `ratio` is `1.0` it means 0% of the current color and 100% of the `other` color (0:1 mixture)
|
|
165
|
+
"""
|
|
162
166
|
if not (isinstance(ratio, (int, float)) and 0 <= ratio <= 1):
|
|
163
167
|
raise ValueError("'ratio' must be a float/int in [0.0, 1.0]")
|
|
164
168
|
elif not isinstance(other, rgba):
|
|
@@ -177,7 +181,13 @@ class rgba:
|
|
|
177
181
|
if additive_alpha:
|
|
178
182
|
self.a = max(0, min(1, (self_a * (2 - ratio)) + (other_a * ratio)))
|
|
179
183
|
else:
|
|
180
|
-
self.a = max(
|
|
184
|
+
self.a = max(
|
|
185
|
+
0,
|
|
186
|
+
min(
|
|
187
|
+
1,
|
|
188
|
+
(self_a * (1 - (ratio / 2))) + (other_a * (ratio / 2)),
|
|
189
|
+
),
|
|
190
|
+
)
|
|
181
191
|
else:
|
|
182
192
|
self.a = None
|
|
183
193
|
return rgba(self.r, self.g, self.b, None if none_alpha else self.a)
|
|
@@ -198,17 +208,17 @@ class rgba:
|
|
|
198
208
|
"""Returns `True` if the color has no transparency"""
|
|
199
209
|
return self.a == 1 or self.a is None
|
|
200
210
|
|
|
201
|
-
def with_alpha(self, alpha:float) ->
|
|
211
|
+
def with_alpha(self, alpha: float) -> "rgba":
|
|
202
212
|
"""Returns a new color with the specified alpha value"""
|
|
203
213
|
if not (isinstance(alpha, (int, float)) and 0 <= alpha <= 1):
|
|
204
214
|
raise ValueError("'alpha' must be a float/int in [0.0, 1.0]")
|
|
205
215
|
return rgba(self.r, self.g, self.b, alpha)
|
|
206
216
|
|
|
207
|
-
def complementary(self) ->
|
|
217
|
+
def complementary(self) -> "rgba":
|
|
208
218
|
"""Returns the complementary color (180 degrees on the color wheel)"""
|
|
209
219
|
return self.to_hsla().complementary().to_rgba()
|
|
210
220
|
|
|
211
|
-
def _rgb_to_hsl(self, r:int, g:int, b:int) -> tuple:
|
|
221
|
+
def _rgb_to_hsl(self, r: int, g: int, b: int) -> tuple:
|
|
212
222
|
r, g, b = r / 255.0, g / 255.0, b / 255.0
|
|
213
223
|
max_c, min_c = max(r, g, b), min(r, g, b)
|
|
214
224
|
l = (max_c + min_c) / 2
|
|
@@ -217,15 +227,16 @@ class rgba:
|
|
|
217
227
|
else:
|
|
218
228
|
delta = max_c - min_c
|
|
219
229
|
s = delta / (1 - abs(2 * l - 1))
|
|
220
|
-
if max_c == r:
|
|
221
|
-
|
|
222
|
-
|
|
230
|
+
if max_c == r:
|
|
231
|
+
h = ((g - b) / delta) % 6
|
|
232
|
+
elif max_c == g:
|
|
233
|
+
h = ((b - r) / delta) + 2
|
|
234
|
+
else:
|
|
235
|
+
h = ((r - g) / delta) + 4
|
|
223
236
|
h /= 6
|
|
224
237
|
return int(round(h * 360)), int(round(s * 100)), int(round(l * 100))
|
|
225
238
|
|
|
226
239
|
|
|
227
|
-
|
|
228
|
-
|
|
229
240
|
class hsla:
|
|
230
241
|
"""A HSL/HSLA color: is a tuple of 3 integers, representing hue (`0`-`360`), saturation (`0`-`100`), and lightness (`0`-`100`).<br>
|
|
231
242
|
Also includes an optional 4th param, which is a float, that represents the alpha channel (`0.0`-`1.0`).\n
|
|
@@ -249,14 +260,18 @@ class hsla:
|
|
|
249
260
|
- `with_alpha(alpha)` to create a new color with different alpha
|
|
250
261
|
- `complementary()` to get the complementary color"""
|
|
251
262
|
|
|
252
|
-
def __init__(self, h:int, s:int, l:int, a:float = None):
|
|
263
|
+
def __init__(self, h: int, s: int, l: int, a: float = None):
|
|
253
264
|
if any(isinstance(x, hsla) for x in (h, s, l)):
|
|
254
|
-
raise ValueError(
|
|
265
|
+
raise ValueError("Color is already a hsla() color")
|
|
255
266
|
elif not (isinstance(h, int) and (0 <= h <= 360) and all(isinstance(x, int) and (0 <= x <= 100) for x in (s, l))):
|
|
256
|
-
raise ValueError(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
267
|
+
raise ValueError(
|
|
268
|
+
"HSL color must have H as integer in [0, 360] and S L as integers in [0, 100]: got",
|
|
269
|
+
(h, s, l),
|
|
270
|
+
)
|
|
271
|
+
elif a is not None and (not isinstance(a, (int, float)) or not 0 <= a <= 1):
|
|
272
|
+
raise ValueError("Alpha channel must be a float/int in [0.0, 1.0]: got", a)
|
|
273
|
+
self.h, self.s, self.l = h, s, l
|
|
274
|
+
self.a = (1.0 if a > 1.0 else float(a)) if a else None
|
|
260
275
|
|
|
261
276
|
def __len__(self):
|
|
262
277
|
return 4 if self.a else 3
|
|
@@ -264,6 +279,9 @@ class hsla:
|
|
|
264
279
|
def __iter__(self):
|
|
265
280
|
return iter((self.h, self.s, self.l) + ((self.a,) if self.a else ()))
|
|
266
281
|
|
|
282
|
+
def __dict__(self):
|
|
283
|
+
return self.dict()
|
|
284
|
+
|
|
267
285
|
def __getitem__(self, index):
|
|
268
286
|
return ((self.h, self.s, self.l) + ((self.a,) if self.a else ()))[index]
|
|
269
287
|
|
|
@@ -276,15 +294,12 @@ class hsla:
|
|
|
276
294
|
def __eq__(self, other):
|
|
277
295
|
if not isinstance(other, hsla):
|
|
278
296
|
return False
|
|
279
|
-
return (self.h, self.s, self.l, self.a) == (
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
def tuple(self) -> tuple:
|
|
286
|
-
"""Returns the color components as a tuple `(h, s, l)` or `(h, s, l, a)` if alpha is present"""
|
|
287
|
-
return tuple(self.list())
|
|
297
|
+
return (self.h, self.s, self.l, self.a) == (
|
|
298
|
+
other[0],
|
|
299
|
+
other[1],
|
|
300
|
+
other[2],
|
|
301
|
+
other[3],
|
|
302
|
+
)
|
|
288
303
|
|
|
289
304
|
def dict(self) -> dict:
|
|
290
305
|
"""Returns the color components as a dictionary with keys `'h'`, `'s'`, `'l'` and optionally `'a'`"""
|
|
@@ -294,71 +309,73 @@ class hsla:
|
|
|
294
309
|
"""Returns the color components as separate values `h, s, l, a`"""
|
|
295
310
|
return self.h, self.s, self.l, self.a
|
|
296
311
|
|
|
297
|
-
def to_rgba(self) ->
|
|
312
|
+
def to_rgba(self) -> "rgba":
|
|
298
313
|
"""Returns the color as a `rgba()` color"""
|
|
299
314
|
return rgba(*self._hsl_to_rgb(self.h, self.s, self.l), self.a)
|
|
300
315
|
|
|
301
|
-
def to_hexa(self) ->
|
|
316
|
+
def to_hexa(self) -> "hexa":
|
|
302
317
|
"""Returns the color as a `hexa()` color"""
|
|
303
318
|
r, g, b = self._hsl_to_rgb(self.h, self.s, self.l)
|
|
304
319
|
return hexa(f'#{r:02X}{g:02X}{b:02X}{f"{int(self.a * 255):02X}" if self.a else ""}')
|
|
305
320
|
|
|
306
321
|
def has_alpha(self) -> bool:
|
|
307
322
|
"""Returns `True` if the color has an alpha channel and `False` otherwise"""
|
|
308
|
-
return self.a
|
|
323
|
+
return self.a is not None
|
|
309
324
|
|
|
310
|
-
def lighten(self, amount:float) ->
|
|
325
|
+
def lighten(self, amount: float) -> "hsla":
|
|
311
326
|
"""Increases the colors lightness by the specified amount (`0.0`-`1.0`)"""
|
|
312
327
|
if not (isinstance(amount, (int, float)) and 0 <= amount <= 1):
|
|
313
328
|
raise ValueError("'amount' must be a float/int in [0.0, 1.0]")
|
|
314
329
|
self.l = int(min(100, self.l + (100 - self.l) * amount))
|
|
315
330
|
return hsla(self.h, self.s, self.l, self.a)
|
|
316
331
|
|
|
317
|
-
def darken(self, amount:float) ->
|
|
332
|
+
def darken(self, amount: float) -> "hsla":
|
|
318
333
|
"""Decreases the colors lightness by the specified amount (`0.0`-`1.0`)"""
|
|
319
334
|
if not (isinstance(amount, (int, float)) and 0 <= amount <= 1):
|
|
320
335
|
raise ValueError("'amount' must be a float/int in [0.0, 1.0]")
|
|
321
336
|
self.l = int(max(0, self.l * (1 - amount)))
|
|
322
337
|
return hsla(self.h, self.s, self.l, self.a)
|
|
323
338
|
|
|
324
|
-
def saturate(self, amount:float) ->
|
|
339
|
+
def saturate(self, amount: float) -> "hsla":
|
|
325
340
|
"""Increases the colors saturation by the specified amount (`0.0`-`1.0`)"""
|
|
326
341
|
if not (isinstance(amount, (int, float)) and 0 <= amount <= 1):
|
|
327
342
|
raise ValueError("'amount' must be a float/int in [0.0, 1.0]")
|
|
328
343
|
self.s = int(min(100, self.s + (100 - self.s) * amount))
|
|
329
344
|
return hsla(self.h, self.s, self.l, self.a)
|
|
330
345
|
|
|
331
|
-
def desaturate(self, amount:float) ->
|
|
346
|
+
def desaturate(self, amount: float) -> "hsla":
|
|
332
347
|
"""Decreases the colors saturation by the specified amount (`0.0`-`1.0`)"""
|
|
333
348
|
if not (isinstance(amount, (int, float)) and 0 <= amount <= 1):
|
|
334
349
|
raise ValueError("'amount' must be a float/int in [0.0, 1.0]")
|
|
335
350
|
self.s = int(max(0, self.s * (1 - amount)))
|
|
336
351
|
return hsla(self.h, self.s, self.l, self.a)
|
|
337
352
|
|
|
338
|
-
def rotate(self, degrees:int) ->
|
|
353
|
+
def rotate(self, degrees: int) -> "hsla":
|
|
339
354
|
"""Rotates the colors hue by the specified number of degrees"""
|
|
340
355
|
self.h = (self.h + degrees) % 360
|
|
341
356
|
return hsla(self.h, self.s, self.l, self.a)
|
|
342
357
|
|
|
343
|
-
def invert(self, invert_alpha:bool = False) ->
|
|
358
|
+
def invert(self, invert_alpha: bool = False) -> "hsla":
|
|
344
359
|
"""Inverts the color by rotating hue by 180 degrees and inverting lightness"""
|
|
345
360
|
self.h = (self.h + 180) % 360
|
|
346
361
|
self.l = 100 - self.l
|
|
347
|
-
if invert_alpha:
|
|
362
|
+
if invert_alpha:
|
|
363
|
+
self.a = 1 - self.a
|
|
348
364
|
return hsla(self.h, self.s, self.l, self.a)
|
|
349
365
|
|
|
350
|
-
def grayscale(self) ->
|
|
366
|
+
def grayscale(self) -> "hsla":
|
|
351
367
|
"""Converts the color to grayscale using the luminance formula"""
|
|
352
368
|
l = Color.luminance(*self._hsl_to_rgb(self.h, self.s, self.l))
|
|
353
369
|
self.h, self.s, self.l, _ = rgba(l, l, l).to_hsla().values()
|
|
354
370
|
return hsla(self.h, self.s, self.l, self.a)
|
|
355
371
|
|
|
356
|
-
def blend(self, other:
|
|
372
|
+
def blend(self, other: "hsla", ratio: float = 0.5, additive_alpha: bool = False) -> "rgba":
|
|
357
373
|
"""Blends the current color with another color using the specified ratio (`0.0`-`1.0`):<br>
|
|
358
374
|
If `ratio` is `0.0` it means 100% of the current color and 0% of the `other` color (1:0 mixture)<br>
|
|
359
375
|
If `ratio` is `0.5` it means 50% of both colors (1:1 mixture)<br>
|
|
360
|
-
If `ratio` is `1.0` it means 0% of the current color and 100% of the `other` color (0:1 mixture)
|
|
361
|
-
|
|
376
|
+
If `ratio` is `1.0` it means 0% of the current color and 100% of the `other` color (0:1 mixture)
|
|
377
|
+
"""
|
|
378
|
+
self.h, self.s, self.l, self.a = self.to_rgba().blend(Color.to_rgba(other), ratio, additive_alpha).to_hsla().values()
|
|
362
379
|
return hsla(self.h, self.s, self.l, self.a)
|
|
363
380
|
|
|
364
381
|
def is_dark(self) -> bool:
|
|
@@ -377,38 +394,43 @@ class hsla:
|
|
|
377
394
|
"""Returns `True` if the color has no transparency"""
|
|
378
395
|
return self.a == 1 or self.a is None
|
|
379
396
|
|
|
380
|
-
def with_alpha(self, alpha:float) ->
|
|
397
|
+
def with_alpha(self, alpha: float) -> "hsla":
|
|
381
398
|
"""Returns a new color with the specified alpha value"""
|
|
382
399
|
if not (isinstance(alpha, (int, float)) and 0 <= alpha <= 1):
|
|
383
400
|
raise ValueError("'alpha' must be a float/int in [0.0, 1.0]")
|
|
384
401
|
return hsla(self.h, self.s, self.l, alpha)
|
|
385
402
|
|
|
386
|
-
def complementary(self) ->
|
|
403
|
+
def complementary(self) -> "hsla":
|
|
387
404
|
"""Returns the complementary color (180 degrees on the color wheel)"""
|
|
388
405
|
return hsla((self.h + 180) % 360, self.s, self.l, self.a)
|
|
389
406
|
|
|
390
|
-
def _hsl_to_rgb(self, h:int, s:int, l:int) -> tuple:
|
|
407
|
+
def _hsl_to_rgb(self, h: int, s: int, l: int) -> tuple:
|
|
391
408
|
h, s, l = h / 360, s / 100, l / 100
|
|
392
409
|
if s == 0:
|
|
393
410
|
r = g = b = int(l * 255)
|
|
394
411
|
else:
|
|
412
|
+
|
|
395
413
|
def hue_to_rgb(p, q, t):
|
|
396
|
-
if t < 0:
|
|
397
|
-
|
|
398
|
-
if t
|
|
399
|
-
|
|
400
|
-
if t <
|
|
414
|
+
if t < 0:
|
|
415
|
+
t += 1
|
|
416
|
+
if t > 1:
|
|
417
|
+
t -= 1
|
|
418
|
+
if t < 1 / 6:
|
|
419
|
+
return p + (q - p) * 6 * t
|
|
420
|
+
if t < 1 / 2:
|
|
421
|
+
return q
|
|
422
|
+
if t < 2 / 3:
|
|
423
|
+
return p + (q - p) * (2 / 3 - t) * 6
|
|
401
424
|
return p
|
|
425
|
+
|
|
402
426
|
q = l * (1 + s) if l < 0.5 else l + s - l * s
|
|
403
427
|
p = 2 * l - q
|
|
404
|
-
r = int(round(hue_to_rgb(p, q, h + 1/3) * 255))
|
|
428
|
+
r = int(round(hue_to_rgb(p, q, h + 1 / 3) * 255))
|
|
405
429
|
g = int(round(hue_to_rgb(p, q, h) * 255))
|
|
406
|
-
b = int(round(hue_to_rgb(p, q, h - 1/3) * 255))
|
|
430
|
+
b = int(round(hue_to_rgb(p, q, h - 1 / 3) * 255))
|
|
407
431
|
return r, g, b
|
|
408
432
|
|
|
409
433
|
|
|
410
|
-
|
|
411
|
-
|
|
412
434
|
class hexa:
|
|
413
435
|
"""A HEX color: is a string representing a hexadecimal color code with optional alpha channel.\n
|
|
414
436
|
-------------------------------------------------------------------------------------------------
|
|
@@ -432,26 +454,46 @@ class hexa:
|
|
|
432
454
|
- `with_alpha(alpha)` to create a new color with different alpha
|
|
433
455
|
- `complementary()` to get the complementary color"""
|
|
434
456
|
|
|
435
|
-
def __init__(self, color:str|int):
|
|
457
|
+
def __init__(self, color: str | int):
|
|
436
458
|
if isinstance(color, hexa):
|
|
437
|
-
raise ValueError(
|
|
459
|
+
raise ValueError("Color is already a hexa() color")
|
|
438
460
|
if isinstance(color, str):
|
|
439
|
-
if color.startswith(
|
|
461
|
+
if color.startswith("#"):
|
|
440
462
|
color = color[1:].upper()
|
|
441
|
-
elif color.startswith(
|
|
463
|
+
elif color.startswith("0x"):
|
|
442
464
|
color = color[2:].upper()
|
|
443
465
|
if len(color) == 3: # RGB
|
|
444
|
-
self.r, self.g, self.b, self.a =
|
|
466
|
+
self.r, self.g, self.b, self.a = (
|
|
467
|
+
int(color[0] * 2, 16),
|
|
468
|
+
int(color[1] * 2, 16),
|
|
469
|
+
int(color[2] * 2, 16),
|
|
470
|
+
None,
|
|
471
|
+
)
|
|
445
472
|
elif len(color) == 4: # RGBA
|
|
446
|
-
self.r, self.g, self.b, self.a =
|
|
473
|
+
self.r, self.g, self.b, self.a = (
|
|
474
|
+
int(color[0] * 2, 16),
|
|
475
|
+
int(color[1] * 2, 16),
|
|
476
|
+
int(color[2] * 2, 16),
|
|
477
|
+
int(color[3] * 2, 16) / 255.0,
|
|
478
|
+
)
|
|
447
479
|
elif len(color) == 6: # RRGGBB
|
|
448
|
-
self.r, self.g, self.b, self.a =
|
|
480
|
+
self.r, self.g, self.b, self.a = (
|
|
481
|
+
int(color[0:2], 16),
|
|
482
|
+
int(color[2:4], 16),
|
|
483
|
+
int(color[4:6], 16),
|
|
484
|
+
None,
|
|
485
|
+
)
|
|
449
486
|
elif len(color) == 8: # RRGGBBAA
|
|
450
|
-
self.r, self.g, self.b, self.a =
|
|
487
|
+
self.r, self.g, self.b, self.a = (
|
|
488
|
+
int(color[0:2], 16),
|
|
489
|
+
int(color[2:4], 16),
|
|
490
|
+
int(color[4:6], 16),
|
|
491
|
+
int(color[6:8], 16) / 255.0,
|
|
492
|
+
)
|
|
451
493
|
else:
|
|
452
494
|
raise ValueError(f"Invalid HEX format '{color}'")
|
|
453
495
|
elif isinstance(color, int):
|
|
454
|
-
self.r, self.g, self.b, self.a = Color.
|
|
496
|
+
self.r, self.g, self.b, self.a = Color.hex_int_to_rgba(color)
|
|
455
497
|
else:
|
|
456
498
|
raise TypeError("HEX color must be of type 'str' or 'int': got", type(color))
|
|
457
499
|
|
|
@@ -459,10 +501,13 @@ class hexa:
|
|
|
459
501
|
return 4 if self.a else 3
|
|
460
502
|
|
|
461
503
|
def __iter__(self):
|
|
462
|
-
return iter((f
|
|
504
|
+
return iter((f"{self.r:02X}", f"{self.g:02X}", f"{self.b:02X}") + ((f"{int(self.a * 255):02X}",) if self.a else ()))
|
|
505
|
+
|
|
506
|
+
def __dict__(self):
|
|
507
|
+
return self.dict()
|
|
463
508
|
|
|
464
509
|
def __getitem__(self, index):
|
|
465
|
-
return ((f
|
|
510
|
+
return ((f"{self.r:02X}", f"{self.g:02X}", f"{self.b:02X}") + ((f"{int(self.a * 255):02X}",) if self.a else ()))[index]
|
|
466
511
|
|
|
467
512
|
def __repr__(self):
|
|
468
513
|
return f'hexa(#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}" if self.a else ""})'
|
|
@@ -471,30 +516,42 @@ class hexa:
|
|
|
471
516
|
return f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}" if self.a else ""}'
|
|
472
517
|
|
|
473
518
|
def __eq__(self, other):
|
|
474
|
-
if not isinstance(other, hexa):
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
"""Returns the color components as a tuple of hex strings `(RR, GG, BB)` or `(RR, GG, BB, AA)` if alpha is present"""
|
|
483
|
-
return tuple(self.list())
|
|
519
|
+
if not isinstance(other, hexa):
|
|
520
|
+
return False
|
|
521
|
+
return (self.r, self.g, self.b, self.a) == (
|
|
522
|
+
other[0],
|
|
523
|
+
other[1],
|
|
524
|
+
other[2],
|
|
525
|
+
other[3],
|
|
526
|
+
)
|
|
484
527
|
|
|
485
528
|
def dict(self) -> dict:
|
|
486
529
|
"""Returns the color components as a dictionary with hex string values for keys `'r'`, `'g'`, `'b'` and optionally `'a'`"""
|
|
487
|
-
return
|
|
530
|
+
return (
|
|
531
|
+
dict(
|
|
532
|
+
r=f"{self.r:02X}",
|
|
533
|
+
g=f"{self.g:02X}",
|
|
534
|
+
b=f"{self.b:02X}",
|
|
535
|
+
a=f"{int(self.a * 255):02X}",
|
|
536
|
+
)
|
|
537
|
+
if self.a
|
|
538
|
+
else dict(r=f"{self.r:02X}", g=f"{self.g:02X}", b=f"{self.b:02X}")
|
|
539
|
+
)
|
|
488
540
|
|
|
489
541
|
def values(self) -> tuple:
|
|
490
542
|
"""Returns the color components as separate values `r, g, b, a`"""
|
|
491
543
|
return self.r, self.g, self.b, self.a
|
|
492
544
|
|
|
493
|
-
def to_rgba(self, round_alpha:bool = True) ->
|
|
545
|
+
def to_rgba(self, round_alpha: bool = True) -> "rgba":
|
|
494
546
|
"""Returns the color as a `rgba()` color"""
|
|
495
|
-
return rgba(
|
|
496
|
-
|
|
497
|
-
|
|
547
|
+
return rgba(
|
|
548
|
+
self.r,
|
|
549
|
+
self.g,
|
|
550
|
+
self.b,
|
|
551
|
+
(round(self.a, 2) if round_alpha else self.a) if self.a else None,
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
def to_hsla(self, round_alpha: bool = True) -> "hsla":
|
|
498
555
|
"""Returns the color as a `hsla()` color"""
|
|
499
556
|
return self.to_rgba(round_alpha).to_hsla()
|
|
500
557
|
|
|
@@ -502,47 +559,49 @@ class hexa:
|
|
|
502
559
|
"""Returns `True` if the color has an alpha channel and `False` otherwise"""
|
|
503
560
|
return self.a is not None
|
|
504
561
|
|
|
505
|
-
def lighten(self, amount:float) ->
|
|
562
|
+
def lighten(self, amount: float) -> "hexa":
|
|
506
563
|
"""Increases the colors lightness by the specified amount (`0.0`-`1.0`)"""
|
|
507
564
|
self.r, self.g, self.b, self.a = self.to_rgba(False).lighten(amount).values()
|
|
508
565
|
return hexa(f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}" if self.a else ""}')
|
|
509
566
|
|
|
510
|
-
def darken(self, amount:float) ->
|
|
567
|
+
def darken(self, amount: float) -> "hexa":
|
|
511
568
|
"""Decreases the colors lightness by the specified amount (`0.0`-`1.0`)"""
|
|
512
569
|
self.r, self.g, self.b, self.a = self.to_rgba(False).darken(amount).values()
|
|
513
570
|
return hexa(f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}" if self.a else ""}')
|
|
514
571
|
|
|
515
|
-
def saturate(self, amount:float) ->
|
|
572
|
+
def saturate(self, amount: float) -> "hexa":
|
|
516
573
|
"""Increases the colors saturation by the specified amount (`0.0`-`1.0`)"""
|
|
517
574
|
self.r, self.g, self.b, self.a = self.to_rgba(False).saturate(amount).values()
|
|
518
575
|
return hexa(f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}" if self.a else ""}')
|
|
519
576
|
|
|
520
|
-
def desaturate(self, amount:float) ->
|
|
577
|
+
def desaturate(self, amount: float) -> "hexa":
|
|
521
578
|
"""Decreases the colors saturation by the specified amount (`0.0`-`1.0`)"""
|
|
522
579
|
self.r, self.g, self.b, self.a = self.to_rgba(False).desaturate(amount).values()
|
|
523
580
|
return hexa(f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}" if self.a else ""}')
|
|
524
581
|
|
|
525
|
-
def rotate(self, degrees:int) ->
|
|
582
|
+
def rotate(self, degrees: int) -> "hexa":
|
|
526
583
|
"""Rotates the colors hue by the specified number of degrees"""
|
|
527
584
|
self.r, self.g, self.b, self.a = self.to_rgba(False).rotate(degrees).values()
|
|
528
585
|
return hexa(f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}" if self.a else ""}')
|
|
529
586
|
|
|
530
|
-
def invert(self, invert_alpha:bool = False) ->
|
|
587
|
+
def invert(self, invert_alpha: bool = False) -> "hexa":
|
|
531
588
|
"""Inverts the color by rotating hue by 180 degrees and inverting lightness"""
|
|
532
589
|
self.r, self.g, self.b, self.a = self.to_rgba(False).invert().values()
|
|
533
|
-
if invert_alpha:
|
|
590
|
+
if invert_alpha:
|
|
591
|
+
self.a = 1 - self.a
|
|
534
592
|
return hexa(f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}" if self.a else ""}')
|
|
535
593
|
|
|
536
|
-
def grayscale(self) ->
|
|
594
|
+
def grayscale(self) -> "hexa":
|
|
537
595
|
"""Converts the color to grayscale using the luminance formula"""
|
|
538
596
|
self.r = self.g = self.b = Color.luminance(self.r, self.g, self.b)
|
|
539
597
|
return hexa(f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}" if self.a else ""}')
|
|
540
598
|
|
|
541
|
-
def blend(self, other:
|
|
599
|
+
def blend(self, other: "hexa", ratio: float = 0.5, additive_alpha: bool = False) -> "rgba":
|
|
542
600
|
"""Blends the current color with another color using the specified ratio (`0.0`-`1.0`):<br>
|
|
543
601
|
If `ratio` is `0.0` it means 100% of the current color and 0% of the `other` color (1:0 mixture)<br>
|
|
544
602
|
If `ratio` is `0.5` it means 50% of both colors (1:1 mixture)<br>
|
|
545
|
-
If `ratio` is `1.0` it means 0% of the current color and 100% of the `other` color (0:1 mixture)
|
|
603
|
+
If `ratio` is `1.0` it means 0% of the current color and 100% of the `other` color (0:1 mixture)
|
|
604
|
+
"""
|
|
546
605
|
self.r, self.g, self.b, self.a = self.to_rgba(False).blend(Color.to_rgba(other), ratio, additive_alpha).values()
|
|
547
606
|
return hexa(f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}" if self.a else ""}')
|
|
548
607
|
|
|
@@ -562,105 +621,133 @@ class hexa:
|
|
|
562
621
|
"""Returns `True` if the color has no transparency (`alpha == 1.0`)"""
|
|
563
622
|
return self.to_hsla(False).is_opaque()
|
|
564
623
|
|
|
565
|
-
def with_alpha(self, alpha:float) ->
|
|
624
|
+
def with_alpha(self, alpha: float) -> "hexa":
|
|
566
625
|
"""Returns a new color with the specified alpha value"""
|
|
567
626
|
if not (isinstance(alpha, (int, float)) and 0 <= alpha <= 1):
|
|
568
627
|
raise ValueError("'alpha' must be in [0.0, 1.0]")
|
|
569
|
-
return hexa(f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(
|
|
628
|
+
return hexa(f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(alpha * 255):02X}" if alpha else ""}')
|
|
570
629
|
|
|
571
|
-
def complementary(self) ->
|
|
630
|
+
def complementary(self) -> "hexa":
|
|
572
631
|
"""Returns the complementary color (180 degrees on the color wheel)"""
|
|
573
632
|
return self.to_hsla(False).complementary().to_hexa()
|
|
574
633
|
|
|
575
634
|
|
|
576
|
-
|
|
577
|
-
|
|
578
635
|
class Color:
|
|
579
636
|
|
|
580
637
|
@staticmethod
|
|
581
|
-
def is_valid_rgba(color:str|list|tuple|dict, allow_alpha:bool = True) -> bool:
|
|
638
|
+
def is_valid_rgba(color: str | list | tuple | dict, allow_alpha: bool = True) -> bool:
|
|
582
639
|
try:
|
|
583
640
|
if isinstance(color, rgba):
|
|
584
641
|
return True
|
|
585
642
|
elif isinstance(color, (list, tuple)):
|
|
586
643
|
if allow_alpha and Color.has_alpha(color):
|
|
587
|
-
return
|
|
644
|
+
return (
|
|
645
|
+
0 <= color[0] <= 255
|
|
646
|
+
and 0 <= color[1] <= 255
|
|
647
|
+
and 0 <= color[2] <= 255
|
|
648
|
+
and (0 <= color[3] <= 1 or color[3] is None)
|
|
649
|
+
)
|
|
588
650
|
return 0 <= color[0] <= 255 and 0 <= color[1] <= 255 and 0 <= color[2] <= 255
|
|
589
651
|
elif isinstance(color, dict):
|
|
590
652
|
if allow_alpha and Color.has_alpha(color):
|
|
591
|
-
return
|
|
592
|
-
|
|
653
|
+
return (
|
|
654
|
+
0 <= color["r"] <= 255
|
|
655
|
+
and 0 <= color["g"] <= 255
|
|
656
|
+
and 0 <= color["b"] <= 255
|
|
657
|
+
and (0 <= color["a"] <= 1 or color["a"] is None)
|
|
658
|
+
)
|
|
659
|
+
return 0 <= color["r"] <= 255 and 0 <= color["g"] <= 255 and 0 <= color["b"] <= 255
|
|
593
660
|
elif isinstance(color, str):
|
|
594
661
|
return bool(_re.fullmatch(Regex.rgba_str(), color))
|
|
595
662
|
return False
|
|
596
|
-
except:
|
|
663
|
+
except Exception:
|
|
664
|
+
return False
|
|
597
665
|
|
|
598
666
|
@staticmethod
|
|
599
|
-
def is_valid_hsla(color:str|list|tuple|dict, allow_alpha:bool = True) -> bool:
|
|
667
|
+
def is_valid_hsla(color: str | list | tuple | dict, allow_alpha: bool = True) -> bool:
|
|
600
668
|
try:
|
|
601
669
|
if isinstance(color, hsla):
|
|
602
670
|
return True
|
|
603
671
|
elif isinstance(color, (list, tuple)):
|
|
604
672
|
if allow_alpha and Color.has_alpha(color):
|
|
605
|
-
return
|
|
673
|
+
return (
|
|
674
|
+
0 <= color[0] <= 360
|
|
675
|
+
and 0 <= color[1] <= 100
|
|
676
|
+
and 0 <= color[2] <= 100
|
|
677
|
+
and (0 <= color[3] <= 1 or color[3] is None)
|
|
678
|
+
)
|
|
606
679
|
else:
|
|
607
680
|
return 0 <= color[0] <= 360 and 0 <= color[1] <= 100 and 0 <= color[2] <= 100
|
|
608
681
|
elif isinstance(color, dict):
|
|
609
682
|
if allow_alpha and Color.has_alpha(color):
|
|
610
|
-
return
|
|
683
|
+
return (
|
|
684
|
+
0 <= color["h"] <= 360
|
|
685
|
+
and 0 <= color["s"] <= 100
|
|
686
|
+
and 0 <= color["l"] <= 100
|
|
687
|
+
and (0 <= color["a"] <= 1 or color["a"] is None)
|
|
688
|
+
)
|
|
611
689
|
else:
|
|
612
|
-
return 0 <= color[
|
|
690
|
+
return 0 <= color["h"] <= 360 and 0 <= color["s"] <= 100 and 0 <= color["l"] <= 100
|
|
613
691
|
elif isinstance(color, str):
|
|
614
692
|
return bool(_re.fullmatch(Regex.hsla_str(), color))
|
|
615
|
-
except:
|
|
693
|
+
except Exception:
|
|
616
694
|
return False
|
|
617
695
|
|
|
618
696
|
@staticmethod
|
|
619
|
-
def is_valid_hexa(color:str, allow_alpha:bool = True, get_prefix:bool = False) -> bool|tuple[bool,str]:
|
|
697
|
+
def is_valid_hexa(color: str, allow_alpha: bool = True, get_prefix: bool = False) -> bool | tuple[bool, str]:
|
|
620
698
|
try:
|
|
621
699
|
if isinstance(color, hexa):
|
|
622
|
-
return (True,
|
|
700
|
+
return (True, "#")
|
|
623
701
|
elif isinstance(color, int):
|
|
624
702
|
is_valid = 0 <= color <= (0xFFFFFFFF if allow_alpha else 0xFFFFFF)
|
|
625
|
-
return (is_valid,
|
|
703
|
+
return (is_valid, "0x") if get_prefix else is_valid
|
|
626
704
|
else:
|
|
627
|
-
if color.startswith(
|
|
628
|
-
color, prefix = color[1:],
|
|
629
|
-
elif color.startswith(
|
|
630
|
-
color, prefix = color[2:],
|
|
631
|
-
pattern =
|
|
705
|
+
if color.startswith("#"):
|
|
706
|
+
color, prefix = color[1:], "#"
|
|
707
|
+
elif color.startswith("0x"):
|
|
708
|
+
color, prefix = color[2:], "0x"
|
|
709
|
+
pattern = (
|
|
710
|
+
r"(?i)^[0-9A-F]{8}|[0-9A-F]{6}|[0-9A-F]{4}|[0-9A-F]{3}$"
|
|
711
|
+
if allow_alpha
|
|
712
|
+
else r"(?i)^[0-9A-F]{6}|[0-9A-F]{3}$"
|
|
713
|
+
)
|
|
632
714
|
return (bool(_re.fullmatch(pattern, color)), prefix) if get_prefix else bool(_re.fullmatch(pattern, color))
|
|
633
|
-
except:
|
|
715
|
+
except Exception:
|
|
634
716
|
return (False, None) if get_prefix else False
|
|
635
717
|
|
|
636
718
|
@staticmethod
|
|
637
|
-
def is_valid(color:str|list|tuple|dict, allow_alpha:bool = True) -> bool:
|
|
638
|
-
return
|
|
719
|
+
def is_valid(color: str | list | tuple | dict, allow_alpha: bool = True) -> bool:
|
|
720
|
+
return (
|
|
721
|
+
Color.is_valid_rgba(color, allow_alpha)
|
|
722
|
+
or Color.is_valid_hsla(color, allow_alpha)
|
|
723
|
+
or Color.is_valid_hexa(color, allow_alpha)
|
|
724
|
+
)
|
|
639
725
|
|
|
640
726
|
@staticmethod
|
|
641
|
-
def has_alpha(color:rgba|hsla|hexa) -> bool:
|
|
727
|
+
def has_alpha(color: rgba | hsla | hexa) -> bool:
|
|
642
728
|
"""Check if the given color has an alpha channel.\n
|
|
643
729
|
--------------------------------------------------------------------------------
|
|
644
730
|
Input a RGBA, HSLA or HEXA color as `color`.<br>
|
|
645
|
-
Returns `True` if the color has an alpha channel and `False` otherwise.
|
|
731
|
+
Returns `True` if the color has an alpha channel and `False` otherwise.
|
|
732
|
+
"""
|
|
646
733
|
if isinstance(color, (rgba, hsla, hexa)):
|
|
647
734
|
return color.has_alpha()
|
|
648
735
|
if Color.is_valid_hexa(color):
|
|
649
736
|
if isinstance(color, str):
|
|
650
|
-
if color.startswith(
|
|
737
|
+
if color.startswith("#"):
|
|
651
738
|
color = color[1:]
|
|
652
739
|
return len(color) == 4 or len(color) == 8
|
|
653
740
|
if isinstance(color, int):
|
|
654
|
-
hex_length = len(f
|
|
741
|
+
hex_length = len(f"{color:X}")
|
|
655
742
|
return hex_length == 4 or hex_length == 8
|
|
656
743
|
elif isinstance(color, (list, tuple)) and len(color) == 4 and color[3] is not None:
|
|
657
744
|
return True
|
|
658
|
-
elif isinstance(color, dict) and len(color) == 4 and color[
|
|
745
|
+
elif isinstance(color, dict) and len(color) == 4 and color["a"] is not None:
|
|
659
746
|
return True
|
|
660
747
|
return False
|
|
661
748
|
|
|
662
749
|
@staticmethod
|
|
663
|
-
def to_rgba(color:hsla|hexa) -> rgba:
|
|
750
|
+
def to_rgba(color: hsla | hexa) -> rgba:
|
|
664
751
|
"""Will try to convert any color type to a color of type RGBA."""
|
|
665
752
|
if isinstance(color, (hsla, hexa)):
|
|
666
753
|
return color.to_rgba()
|
|
@@ -669,11 +756,15 @@ class Color:
|
|
|
669
756
|
elif Color.is_valid_hexa(color):
|
|
670
757
|
return hexa(color).to_rgba()
|
|
671
758
|
elif Color.is_valid_rgba(color):
|
|
672
|
-
return
|
|
759
|
+
return (
|
|
760
|
+
color
|
|
761
|
+
if isinstance(color, rgba)
|
|
762
|
+
else (rgba(*color) if Color.has_alpha(color) else rgba(color[0], color[1], color[2]))
|
|
763
|
+
)
|
|
673
764
|
raise ValueError(f"Invalid color format '{color}'")
|
|
674
765
|
|
|
675
766
|
@staticmethod
|
|
676
|
-
def to_hsla(color:rgba|hexa) -> hsla:
|
|
767
|
+
def to_hsla(color: rgba | hexa) -> hsla:
|
|
677
768
|
"""Will try to convert any color type to a color of type HSLA."""
|
|
678
769
|
if isinstance(color, (rgba, hexa)):
|
|
679
770
|
return color.to_hsla()
|
|
@@ -682,11 +773,15 @@ class Color:
|
|
|
682
773
|
elif Color.is_valid_hexa(color):
|
|
683
774
|
return hexa(color).to_hsla()
|
|
684
775
|
elif Color.is_valid_hsla(color):
|
|
685
|
-
return
|
|
776
|
+
return (
|
|
777
|
+
color
|
|
778
|
+
if isinstance(color, hsla)
|
|
779
|
+
else (hsla(*color) if Color.has_alpha(color) else hsla(color[0], color[1], color[2]))
|
|
780
|
+
)
|
|
686
781
|
raise ValueError(f"Invalid color format '{color}'")
|
|
687
782
|
|
|
688
783
|
@staticmethod
|
|
689
|
-
def to_hexa(color:rgba|hsla) -> hexa:
|
|
784
|
+
def to_hexa(color: rgba | hsla) -> hexa:
|
|
690
785
|
"""Will try to convert any color type to a color of type HEXA."""
|
|
691
786
|
if isinstance(color, (rgba, hsla)):
|
|
692
787
|
return color.to_hexa()
|
|
@@ -695,26 +790,43 @@ class Color:
|
|
|
695
790
|
elif Color.is_valid_hsla(color):
|
|
696
791
|
return hsla(*color).to_hexa() if Color.has_alpha(color) else hsla(color[0], color[1], color[2]).to_hexa()
|
|
697
792
|
elif Color.is_valid_hexa(color):
|
|
698
|
-
return color if isinstance(color, hexa) else hexa(f
|
|
793
|
+
return color if isinstance(color, hexa) else hexa(f"#{color}")
|
|
699
794
|
raise ValueError(f"Invalid color format '{color}'")
|
|
700
795
|
|
|
701
796
|
@staticmethod
|
|
702
|
-
def str_to_rgba(string:str, only_first:bool = False) -> rgba|list[rgba]|None:
|
|
797
|
+
def str_to_rgba(string: str, only_first: bool = False) -> rgba | list[rgba] | None:
|
|
703
798
|
"""Will try to recognize RGBA colors inside a string and output the found ones as RGBA objects.<br>
|
|
704
|
-
If `only_first` is `True` only the first found color will be returned (not as a list).
|
|
799
|
+
If `only_first` is `True` only the first found color will be returned (not as a list).
|
|
800
|
+
"""
|
|
705
801
|
matches = _re.findall(Regex.rgb_str(allow_alpha=True), string)
|
|
706
|
-
if not matches:
|
|
707
|
-
|
|
802
|
+
if not matches:
|
|
803
|
+
return None
|
|
804
|
+
result = [
|
|
805
|
+
rgba(
|
|
806
|
+
int(m[0]),
|
|
807
|
+
int(m[1]),
|
|
808
|
+
int(m[2]),
|
|
809
|
+
((int(m[3]) if "." not in m[3] else float(m[3])) if m[3] else None),
|
|
810
|
+
)
|
|
811
|
+
for m in matches
|
|
812
|
+
]
|
|
708
813
|
return result[0] if len(result) == 1 or only_first else result
|
|
709
814
|
|
|
710
815
|
@staticmethod
|
|
711
|
-
def
|
|
816
|
+
def rgba_to_hex_int(
|
|
817
|
+
r: int,
|
|
818
|
+
g: int,
|
|
819
|
+
b: int,
|
|
820
|
+
a: float = None,
|
|
821
|
+
_preserve_original: bool = False,
|
|
822
|
+
) -> int:
|
|
712
823
|
"""Convert RGBA channels to a HEXA integer (alpha is optional).\n
|
|
713
824
|
-------------------------------------------------------------------------------------------------------------------------
|
|
714
825
|
To preserve leading zeros, the function will add a `1` at the beginning, if the HEX value would start with a `0`.<br>
|
|
715
826
|
This could affect the color a little bit, but will make sure, that it won't be interpreted as a completely different<br>
|
|
716
827
|
color, when initializing it as a `hexa()` color or changing it back to RGBA using `Color.hex_to_rgba()`.\n
|
|
717
|
-
⇾ **You can disable this behavior by setting `_preserve_original` to `True`**
|
|
828
|
+
⇾ **You can disable this behavior by setting `_preserve_original` to `True`**
|
|
829
|
+
"""
|
|
718
830
|
r = max(0, min(255, int(r)))
|
|
719
831
|
g = max(0, min(255, int(g)))
|
|
720
832
|
b = max(0, min(255, int(b)))
|
|
@@ -732,21 +844,31 @@ class Color:
|
|
|
732
844
|
return hex_int
|
|
733
845
|
|
|
734
846
|
@staticmethod
|
|
735
|
-
def
|
|
847
|
+
def hex_int_to_rgba(hex_int: int) -> tuple[int, int, int, float | int | None]:
|
|
736
848
|
if not isinstance(hex_int, int):
|
|
737
|
-
raise ValueError(
|
|
738
|
-
hex_str = f
|
|
849
|
+
raise ValueError("Input must be an integer (hex value)")
|
|
850
|
+
hex_str = f"{hex_int:x}"
|
|
739
851
|
if len(hex_str) <= 6:
|
|
740
852
|
hex_str = hex_str.zfill(6)
|
|
741
|
-
return
|
|
853
|
+
return (
|
|
854
|
+
int(hex_str[0:2], 16),
|
|
855
|
+
int(hex_str[2:4], 16),
|
|
856
|
+
int(hex_str[4:6], 16),
|
|
857
|
+
None,
|
|
858
|
+
)
|
|
742
859
|
elif len(hex_str) <= 8:
|
|
743
860
|
hex_str = hex_str.zfill(8)
|
|
744
|
-
return
|
|
861
|
+
return (
|
|
862
|
+
int(hex_str[0:2], 16),
|
|
863
|
+
int(hex_str[2:4], 16),
|
|
864
|
+
int(hex_str[4:6], 16),
|
|
865
|
+
int(hex_str[6:8], 16) / 255.0,
|
|
866
|
+
)
|
|
745
867
|
else:
|
|
746
868
|
raise ValueError(f"Invalid HEX integer '0x{hex_str}': expected in range [0x000000, 0xFFFFFF]")
|
|
747
869
|
|
|
748
870
|
@staticmethod
|
|
749
|
-
def luminance(r:int, g:int, b:int, output_type:type = None) -> int|float:
|
|
871
|
+
def luminance(r: int, g: int, b: int, output_type: type = None) -> int | float:
|
|
750
872
|
"""Gets the colors luminance using the luminance formula.\n
|
|
751
873
|
------------------------------------------------------------
|
|
752
874
|
The param `output_type` can be set to:<br>
|
|
@@ -754,46 +876,72 @@ class Color:
|
|
|
754
876
|
*`float`* =⠀float in [0.0, 1.0]<br>
|
|
755
877
|
`None` =⠀integer in [0, 255]"""
|
|
756
878
|
r, g, b = r / 255.0, g / 255.0, b / 255.0
|
|
757
|
-
if r < 0.03928:
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
if
|
|
762
|
-
|
|
879
|
+
if r < 0.03928:
|
|
880
|
+
r = r / 12.92
|
|
881
|
+
else:
|
|
882
|
+
r = ((r + 0.055) / 1.055) ** 2.4
|
|
883
|
+
if g < 0.03928:
|
|
884
|
+
g = g / 12.92
|
|
885
|
+
else:
|
|
886
|
+
g = ((g + 0.055) / 1.055) ** 2.4
|
|
887
|
+
if b < 0.03928:
|
|
888
|
+
b = b / 12.92
|
|
889
|
+
else:
|
|
890
|
+
b = ((b + 0.055) / 1.055) ** 2.4
|
|
763
891
|
l = 0.2126 * r + 0.7152 * g + 0.0722 * b
|
|
764
892
|
return round(l * 100) if isinstance(output_type, int) else round(l * 255) if output_type is None else l
|
|
765
893
|
|
|
766
894
|
@staticmethod
|
|
767
|
-
def text_color_for_on_bg(
|
|
768
|
-
|
|
895
|
+
def text_color_for_on_bg(
|
|
896
|
+
title_bg_color: rgba | hexa = 0xFFF,
|
|
897
|
+
) -> rgba | hexa:
|
|
898
|
+
(was_hexa, hexa_prefix), was_int = Color.is_valid_hexa(title_bg_color, get_prefix=True), isinstance(
|
|
899
|
+
title_bg_color, int
|
|
900
|
+
)
|
|
769
901
|
title_bg_color = Color.to_rgba(title_bg_color)
|
|
770
902
|
brightness = 0.2126 * title_bg_color[0] + 0.7152 * title_bg_color[1] + 0.0722 * title_bg_color[2]
|
|
771
|
-
return (
|
|
903
|
+
return (
|
|
904
|
+
(hexa(f"{hexa_prefix}FFF") if was_hexa else rgba(255, 255, 255))
|
|
905
|
+
if brightness < 128
|
|
906
|
+
else ((0x000 if was_int else hexa(f"{hexa_prefix}000")) if was_hexa else rgba(0, 0, 0))
|
|
907
|
+
)
|
|
772
908
|
|
|
773
909
|
@staticmethod
|
|
774
|
-
def adjust_lightness(color:rgba|hexa, brightness_change:float) -> rgba|hexa:
|
|
910
|
+
def adjust_lightness(color: rgba | hexa, brightness_change: float) -> rgba | hexa:
|
|
775
911
|
"""In- or decrease the lightness of the input color.\n
|
|
776
912
|
----------------------------------------------------------------------------------------------------
|
|
777
913
|
**color** (rgba|hexa): HEX or RGBA color<br>
|
|
778
914
|
**brightness_change** (float): float between -1.0 (darken by `100%`) and 1.0 (lighten by `100%`)\n
|
|
779
915
|
----------------------------------------------------------------------------------------------------
|
|
780
|
-
**returns** (rgba|hexa): the adjusted color in the format of the input color
|
|
916
|
+
**returns** (rgba|hexa): the adjusted color in the format of the input color
|
|
917
|
+
"""
|
|
781
918
|
was_hexa = Color.is_valid_hexa(color)
|
|
782
919
|
color = Color.to_hsla(color)
|
|
783
|
-
h, s, l, a =
|
|
920
|
+
h, s, l, a = (
|
|
921
|
+
color[0],
|
|
922
|
+
color[1],
|
|
923
|
+
color[2],
|
|
924
|
+
color[3] if Color.has_alpha(color) else None,
|
|
925
|
+
)
|
|
784
926
|
l = int(max(0, min(100, l + brightness_change * 100)))
|
|
785
927
|
return Color.to_hexa((h, s, l, a)) if was_hexa else Color.to_rgba((h, s, l, a))
|
|
786
928
|
|
|
787
929
|
@staticmethod
|
|
788
|
-
def adjust_saturation(color:rgba|hexa, saturation_change:float) -> rgba|hexa:
|
|
930
|
+
def adjust_saturation(color: rgba | hexa, saturation_change: float) -> rgba | hexa:
|
|
789
931
|
"""In- or decrease the saturation of the input color.\n
|
|
790
932
|
---------------------------------------------------------------------------------------------------------
|
|
791
933
|
**color** (rgba|hexa): HEX or RGBA color<br>
|
|
792
934
|
**saturation_change** (float): float between -1.0 (saturate by `100%`) and 1.0 (desaturate by `100%`)\n
|
|
793
935
|
---------------------------------------------------------------------------------------------------------
|
|
794
|
-
**returns** (rgba|hexa): the adjusted color in the format of the input color
|
|
936
|
+
**returns** (rgba|hexa): the adjusted color in the format of the input color
|
|
937
|
+
"""
|
|
795
938
|
was_hexa = Color.is_valid_hexa(color)
|
|
796
939
|
color = Color.to_hsla(color)
|
|
797
|
-
h, s, l, a =
|
|
940
|
+
h, s, l, a = (
|
|
941
|
+
color[0],
|
|
942
|
+
color[1],
|
|
943
|
+
color[2],
|
|
944
|
+
color[3] if Color.has_alpha(color) else None,
|
|
945
|
+
)
|
|
798
946
|
s = int(max(0, min(100, s + saturation_change * 100)))
|
|
799
947
|
return Color.to_hexa((h, s, l, a)) if was_hexa else Color.to_rgba((h, s, l, a))
|