xulbux 1.6.9__py3-none-any.whl → 1.7.1__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 +2 -2
- xulbux/_cli_.py +20 -27
- xulbux/_consts_.py +1 -0
- xulbux/xx_code.py +1 -1
- xulbux/xx_color.py +144 -101
- xulbux/xx_console.py +168 -64
- xulbux/xx_data.py +22 -21
- xulbux/xx_env_path.py +6 -5
- xulbux/xx_file.py +2 -2
- xulbux/xx_format_codes.py +37 -28
- xulbux/xx_json.py +7 -6
- xulbux/xx_path.py +7 -7
- xulbux/xx_regex.py +15 -10
- xulbux/xx_system.py +5 -5
- {xulbux-1.6.9.dist-info → xulbux-1.7.1.dist-info}/METADATA +36 -34
- xulbux-1.7.1.dist-info/RECORD +21 -0
- {xulbux-1.6.9.dist-info → xulbux-1.7.1.dist-info}/WHEEL +1 -1
- xulbux-1.6.9.dist-info/RECORD +0 -21
- {xulbux-1.6.9.dist-info → xulbux-1.7.1.dist-info}/entry_points.txt +0 -0
- {xulbux-1.6.9.dist-info → xulbux-1.7.1.dist-info}/licenses/LICENSE +0 -0
- {xulbux-1.6.9.dist-info → xulbux-1.7.1.dist-info}/top_level.txt +0 -0
xulbux/__init__.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
__version__ = "1.
|
|
1
|
+
__version__ = "1.7.1"
|
|
2
2
|
__author__ = "XulbuX"
|
|
3
3
|
__email__ = "xulbux.real@gmail.com"
|
|
4
4
|
__license__ = "MIT"
|
|
5
|
-
__copyright__ = "Copyright (c)
|
|
5
|
+
__copyright__ = "Copyright (c) 2024 XulbuX"
|
|
6
6
|
__url__ = "https://github.com/XulbuX/PythonLibraryXulbuX"
|
|
7
7
|
__description__ = "A library which includes a lot of really helpful functions."
|
|
8
8
|
__all__ = [
|
xulbux/_cli_.py
CHANGED
|
@@ -7,11 +7,13 @@ from .xx_console import Console
|
|
|
7
7
|
def help_command():
|
|
8
8
|
"""Show some info about the library, with a brief explanation of how to use it."""
|
|
9
9
|
color = {
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
10
|
+
"class": COLOR.tangerine,
|
|
11
|
+
"const": COLOR.red,
|
|
12
|
+
"func": COLOR.cyan,
|
|
13
|
+
"import": COLOR.neongreen,
|
|
14
|
+
"lib": COLOR.orange,
|
|
14
15
|
"punctuators": COLOR.darkgray,
|
|
16
|
+
"code_border": COLOR.gray,
|
|
15
17
|
}
|
|
16
18
|
FormatCodes.print(
|
|
17
19
|
rf""" [_|b|#7075FF] __ __
|
|
@@ -22,29 +24,20 @@ def help_command():
|
|
|
22
24
|
|
|
23
25
|
[i|{COLOR.coral}]A TON OF COOL FUNCTIONS, YOU NEED![*]
|
|
24
26
|
|
|
25
|
-
[b|#
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
[
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
[dim](•) PATH OPERATIONS [{color['lib']}]xx[{color['punctuators']}].[{color['class']}]Path[*]
|
|
40
|
-
[dim](•) FILE OPERATIONS [{color['lib']}]xx[{color['punctuators']}].[{color['class']}]File[*]
|
|
41
|
-
[dim](•) JSON FILE OPERATIONS [{color['lib']}]xx[{color['punctuators']}].[{color['class']}]Json[*]
|
|
42
|
-
[dim](•) SYSTEM ACTIONS [{color['lib']}]xx[{color['punctuators']}].[{color['class']}]System[*]
|
|
43
|
-
[dim](•) MANAGE THE ENV PATH VAR [{color['lib']}]xx[{color['punctuators']}].[{color['class']}]EnvPath[*]
|
|
44
|
-
[dim](•) EASY PRETTY PRINTING [{color['lib']}]xx[{color['punctuators']}].[{color['class']}]FormatCodes[*]
|
|
45
|
-
[dim](•) DATA OPERATIONS [{color['lib']}]xx[{color['punctuators']}].[{color['class']}]Data[*]
|
|
46
|
-
[dim](•) STRING OPERATIONS [{color['lib']}]xx[{color['punctuators']}].[{color['class']}]String[*]
|
|
47
|
-
[dim](•) REGEX PATTERN TEMPLATES [{color['lib']}]xx[{color['punctuators']}].[{color['class']}]Regex[*]
|
|
27
|
+
[b|#FCFCFF]Usage:[*]
|
|
28
|
+
[dim|{color['code_border']}](╭────────────────────────────────────────────────────╮)
|
|
29
|
+
[dim|{color['code_border']}](│) [{color['punctuators']}]# CONSTANTS[*] [dim|{color['code_border']}](│)
|
|
30
|
+
[dim|{color['code_border']}](│) [{color['import']}]from [{color['lib']}]xulbux [{color['import']}]import [{color['const']}]COLOR[{color['punctuators']}], [{color['const']}]CHARS[{color['punctuators']}], [{color['const']}]ANSI[*] [dim|{color['code_border']}](│)
|
|
31
|
+
[dim|{color['code_border']}](│) [{color['punctuators']}]# Classes[*] [dim|{color['code_border']}](│)
|
|
32
|
+
[dim|{color['code_border']}](│) [{color['import']}]from [{color['lib']}]xulbux [{color['import']}]import [{color['class']}]Code[{color['punctuators']}], [{color['class']}]Color[{color['punctuators']}], [{color['class']}]Console[{color['punctuators']}], ...[*] [dim|{color['code_border']}](│)
|
|
33
|
+
[dim|{color['code_border']}](│) [{color['punctuators']}]# types[*] [dim|{color['code_border']}](│)
|
|
34
|
+
[dim|{color['code_border']}](│) [{color['import']}]from [{color['lib']}]xulbux [{color['import']}]import [{color['func']}]rgba[{color['punctuators']}], [{color['func']}]hsla[{color['punctuators']}], [{color['func']}]hexa[*] [dim|{color['code_border']}](│)
|
|
35
|
+
[dim|{color['code_border']}](╰────────────────────────────────────────────────────╯)
|
|
36
|
+
[b|#FCFCFF]Documentation:[*]
|
|
37
|
+
[dim|{color['code_border']}](╭────────────────────────────────────────────────────╮)
|
|
38
|
+
[dim|{color['code_border']}](│) [#DADADD]For more information see the GitHub page. [dim|{color['code_border']}](│)
|
|
39
|
+
[dim|{color['code_border']}](│) [u|#8085FF](https://github.com/XulbuX/PythonLibraryXulbuX/wiki) [dim|{color['code_border']}](│)
|
|
40
|
+
[dim|{color['code_border']}](╰────────────────────────────────────────────────────╯)
|
|
48
41
|
[_]
|
|
49
42
|
[dim](Press any key to exit...)
|
|
50
43
|
""",
|
xulbux/_consts_.py
CHANGED
xulbux/xx_code.py
CHANGED
|
@@ -50,7 +50,7 @@ class Code:
|
|
|
50
50
|
nested_calls = _rx.findall(r"(?i)" + Regex.func_call(), func_attrs)
|
|
51
51
|
if nested_calls:
|
|
52
52
|
nested_func_calls.extend(nested_calls)
|
|
53
|
-
return Data.remove_duplicates(funcs + nested_func_calls)
|
|
53
|
+
return list(Data.remove_duplicates(funcs + nested_func_calls))
|
|
54
54
|
|
|
55
55
|
@staticmethod
|
|
56
56
|
def is_js(code: str, funcs: list = ["__", "$t", "$lang"]) -> bool:
|
xulbux/xx_color.py
CHANGED
|
@@ -33,10 +33,40 @@ The `Color` class, which contains all sorts of different color-related methods:
|
|
|
33
33
|
|
|
34
34
|
from .xx_regex import Regex
|
|
35
35
|
|
|
36
|
-
from typing import Optional
|
|
36
|
+
from typing import Annotated, TypeAlias, Iterator, Optional, Literal, Union, Any, cast
|
|
37
37
|
import re as _re
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
Int_0_100 = Annotated[int, "An integer value between 0 and 100, inclusive."]
|
|
41
|
+
Int_0_255 = Annotated[int, "An integer value between 0 and 255, inclusive."]
|
|
42
|
+
Int_0_360 = Annotated[int, "An integer value between 0 and 360, inclusive."]
|
|
43
|
+
Float_0_1 = Annotated[float, "A float value between 0.0 and 1.0, inclusive."]
|
|
44
|
+
|
|
45
|
+
AnyRgba: TypeAlias = Any
|
|
46
|
+
AnyHsla: TypeAlias = Any
|
|
47
|
+
AnyHexa: TypeAlias = Any
|
|
48
|
+
|
|
49
|
+
Rgba: TypeAlias = Union[
|
|
50
|
+
tuple[Int_0_255, Int_0_255, Int_0_255],
|
|
51
|
+
tuple[Int_0_255, Int_0_255, Int_0_255, Float_0_1],
|
|
52
|
+
list[Int_0_255],
|
|
53
|
+
list[Union[Int_0_255, Float_0_1]],
|
|
54
|
+
dict[str, Union[int, float]],
|
|
55
|
+
"rgba",
|
|
56
|
+
str,
|
|
57
|
+
]
|
|
58
|
+
Hsla: TypeAlias = Union[
|
|
59
|
+
tuple[Int_0_360, Int_0_100, Int_0_100],
|
|
60
|
+
tuple[Int_0_360, Int_0_100, Int_0_100, Float_0_1],
|
|
61
|
+
list[Union[Int_0_360, Int_0_100]],
|
|
62
|
+
list[Union[Int_0_360, Int_0_100, Float_0_1]],
|
|
63
|
+
dict[str, Union[int, float]],
|
|
64
|
+
"hsla",
|
|
65
|
+
str,
|
|
66
|
+
]
|
|
67
|
+
Hexa: TypeAlias = Union[str, int, "hexa"]
|
|
68
|
+
|
|
69
|
+
|
|
40
70
|
class rgba:
|
|
41
71
|
"""An RGB/RGBA color: is a tuple of 3 integers, representing the red (`0`-`255`), green (`0`-`255`), and blue (`0`-`255`).\n
|
|
42
72
|
Also includes an optional 4th param, which is a float, that represents the alpha channel (`0.0`-`1.0`).\n
|
|
@@ -60,7 +90,11 @@ class rgba:
|
|
|
60
90
|
- `with_alpha(alpha)` to create a new color with different alpha
|
|
61
91
|
- `complementary()` to get the complementary color"""
|
|
62
92
|
|
|
63
|
-
def __init__(self, r: int, g: int, b: int, a: float = None, _validate: bool = True):
|
|
93
|
+
def __init__(self, r: int, g: int, b: int, a: Optional[float] = None, _validate: bool = True):
|
|
94
|
+
self.r: int
|
|
95
|
+
self.g: int
|
|
96
|
+
self.b: int
|
|
97
|
+
self.a: Optional[float]
|
|
64
98
|
if not _validate:
|
|
65
99
|
self.r, self.g, self.b, self.a = r, g, b, a
|
|
66
100
|
return
|
|
@@ -79,13 +113,10 @@ class rgba:
|
|
|
79
113
|
def __len__(self) -> int:
|
|
80
114
|
return 3 if self.a is None else 4
|
|
81
115
|
|
|
82
|
-
def __iter__(self) ->
|
|
116
|
+
def __iter__(self) -> Iterator:
|
|
83
117
|
return iter((self.r, self.g, self.b) + (() if self.a is None else (self.a, )))
|
|
84
118
|
|
|
85
|
-
def
|
|
86
|
-
return self.dict()
|
|
87
|
-
|
|
88
|
-
def __getitem__(self, index: int) -> int:
|
|
119
|
+
def __getitem__(self, index: int) -> int | float:
|
|
89
120
|
return ((self.r, self.g, self.b) + (() if self.a is None else (self.a, )))[index]
|
|
90
121
|
|
|
91
122
|
def __repr__(self) -> str:
|
|
@@ -94,7 +125,7 @@ class rgba:
|
|
|
94
125
|
def __str__(self) -> str:
|
|
95
126
|
return f'({self.r}, {self.g}, {self.b}{"" if self.a is None else f", {self.a}"})'
|
|
96
127
|
|
|
97
|
-
def __eq__(self, other: "rgba") -> bool:
|
|
128
|
+
def __eq__(self, other: "rgba") -> bool: # type: ignore[override]
|
|
98
129
|
if not isinstance(other, rgba):
|
|
99
130
|
return False
|
|
100
131
|
return (self.r, self.g, self.b, self.a) == (other.r, other.g, other.b, other.a)
|
|
@@ -109,7 +140,7 @@ class rgba:
|
|
|
109
140
|
|
|
110
141
|
def to_hsla(self) -> "hsla":
|
|
111
142
|
"""Returns the color as a `hsla()` color"""
|
|
112
|
-
return hsla(*self._rgb_to_hsl(self.r, self.g, self.b), self.a, _validate=False)
|
|
143
|
+
return hsla(*self._rgb_to_hsl(self.r, self.g, self.b), self.a, _validate=False) # type: ignore[positional-arguments]
|
|
113
144
|
|
|
114
145
|
def to_hexa(self) -> "hexa":
|
|
115
146
|
"""Returns the color as a `hexa()` color"""
|
|
@@ -147,11 +178,11 @@ class rgba:
|
|
|
147
178
|
def invert(self, invert_alpha: bool = False) -> "rgba":
|
|
148
179
|
"""Inverts the color by rotating hue by 180 degrees and inverting lightness"""
|
|
149
180
|
self.r, self.g, self.b = 255 - self.r, 255 - self.g, 255 - self.b
|
|
150
|
-
if invert_alpha:
|
|
181
|
+
if invert_alpha and self.a is not None:
|
|
151
182
|
self.a = 1 - self.a
|
|
152
183
|
return rgba(self.r, self.g, self.b, self.a, _validate=False)
|
|
153
184
|
|
|
154
|
-
def grayscale(self, method:
|
|
185
|
+
def grayscale(self, method: Literal["wcag2", "wcag3", "simple", "bt601"] = "wcag2") -> "rgba":
|
|
155
186
|
"""Converts the color to grayscale using the luminance formula.\n
|
|
156
187
|
------------------------------------------------------------------
|
|
157
188
|
The `method` is the luminance calculation method to use:
|
|
@@ -159,10 +190,10 @@ class rgba:
|
|
|
159
190
|
- `"wcag3"` Draft WCAG 3.0 standard with improved coefficients
|
|
160
191
|
- `"simple"` Simple arithmetic mean (less accurate)
|
|
161
192
|
- `"bt601"` ITU-R BT.601 standard (older TV standard)"""
|
|
162
|
-
self.r = self.g = self.b = Color.luminance(self.r, self.g, self.b, method=method)
|
|
193
|
+
self.r = self.g = self.b = int(Color.luminance(self.r, self.g, self.b, method=method))
|
|
163
194
|
return rgba(self.r, self.g, self.b, self.a, _validate=False)
|
|
164
195
|
|
|
165
|
-
def blend(self, other:
|
|
196
|
+
def blend(self, other: Rgba, ratio: float = 0.5, additive_alpha: bool = False) -> "rgba":
|
|
166
197
|
"""Blends the current color with another color using the specified ratio (`0.0`-`1.0`):
|
|
167
198
|
- if `ratio` is `0.0` it means 100% of the current color and 0% of the `other` color (2:0 mixture)
|
|
168
199
|
- if `ratio` is `0.5` it means 50% of both colors (1:1 mixture)
|
|
@@ -171,7 +202,7 @@ class rgba:
|
|
|
171
202
|
raise ValueError("'ratio' must be a float/int in [0.0, 1.0]")
|
|
172
203
|
elif not isinstance(other, rgba):
|
|
173
204
|
if Color.is_valid_rgba(other):
|
|
174
|
-
other =
|
|
205
|
+
other = Color.to_rgba(other)
|
|
175
206
|
else:
|
|
176
207
|
raise TypeError("'other' must be a valid RGBA color")
|
|
177
208
|
ratio *= 2
|
|
@@ -217,20 +248,20 @@ class rgba:
|
|
|
217
248
|
return self.to_hsla().complementary().to_rgba()
|
|
218
249
|
|
|
219
250
|
def _rgb_to_hsl(self, r: int, g: int, b: int) -> tuple:
|
|
220
|
-
|
|
221
|
-
max_c, min_c = max(
|
|
251
|
+
_r, _g, _b = r / 255.0, g / 255.0, b / 255.0
|
|
252
|
+
max_c, min_c = max(_r, _g, _b), min(_r, _g, _b)
|
|
222
253
|
l = (max_c + min_c) / 2
|
|
223
254
|
if max_c == min_c:
|
|
224
255
|
h = s = 0
|
|
225
256
|
else:
|
|
226
257
|
delta = max_c - min_c
|
|
227
258
|
s = delta / (1 - abs(2 * l - 1))
|
|
228
|
-
if max_c ==
|
|
229
|
-
h = ((
|
|
230
|
-
elif max_c ==
|
|
231
|
-
h = ((
|
|
259
|
+
if max_c == _r:
|
|
260
|
+
h = ((_g - _b) / delta) % 6
|
|
261
|
+
elif max_c == _g:
|
|
262
|
+
h = ((_b - _r) / delta) + 2
|
|
232
263
|
else:
|
|
233
|
-
h = ((
|
|
264
|
+
h = ((_r - _g) / delta) + 4
|
|
234
265
|
h /= 6
|
|
235
266
|
return int(round(h * 360)), int(round(s * 100)), int(round(l * 100))
|
|
236
267
|
|
|
@@ -258,7 +289,11 @@ class hsla:
|
|
|
258
289
|
- `with_alpha(alpha)` to create a new color with different alpha
|
|
259
290
|
- `complementary()` to get the complementary color"""
|
|
260
291
|
|
|
261
|
-
def __init__(self, h: int, s: int, l: int, a: float = None, _validate: bool = True):
|
|
292
|
+
def __init__(self, h: int, s: int, l: int, a: Optional[float] = None, _validate: bool = True):
|
|
293
|
+
self.h: int
|
|
294
|
+
self.s: int
|
|
295
|
+
self.l: int
|
|
296
|
+
self.a: Optional[float]
|
|
262
297
|
if not _validate:
|
|
263
298
|
self.h, self.s, self.l, self.a = h, s, l, a
|
|
264
299
|
return
|
|
@@ -277,13 +312,10 @@ class hsla:
|
|
|
277
312
|
def __len__(self) -> int:
|
|
278
313
|
return 3 if self.a is None else 4
|
|
279
314
|
|
|
280
|
-
def __iter__(self) ->
|
|
315
|
+
def __iter__(self) -> Iterator:
|
|
281
316
|
return iter((self.h, self.s, self.l) + (() if self.a is None else (self.a, )))
|
|
282
317
|
|
|
283
|
-
def
|
|
284
|
-
return self.dict()
|
|
285
|
-
|
|
286
|
-
def __getitem__(self, index: int) -> int:
|
|
318
|
+
def __getitem__(self, index: int) -> int | float:
|
|
287
319
|
return ((self.h, self.s, self.l) + (() if self.a is None else (self.a, )))[index]
|
|
288
320
|
|
|
289
321
|
def __repr__(self) -> str:
|
|
@@ -292,7 +324,7 @@ class hsla:
|
|
|
292
324
|
def __str__(self) -> str:
|
|
293
325
|
return f'({self.h}°, {self.s}%, {self.l}%{"" if self.a is None else f", {self.a}"})'
|
|
294
326
|
|
|
295
|
-
def __eq__(self, other: "hsla") -> bool:
|
|
327
|
+
def __eq__(self, other: "hsla") -> bool: # type: ignore[override]
|
|
296
328
|
if not isinstance(other, hsla):
|
|
297
329
|
return False
|
|
298
330
|
return (self.h, self.s, self.l, self.a) == (other.h, other.s, other.l, other.a)
|
|
@@ -307,7 +339,7 @@ class hsla:
|
|
|
307
339
|
|
|
308
340
|
def to_rgba(self) -> "rgba":
|
|
309
341
|
"""Returns the color as a `rgba()` color"""
|
|
310
|
-
return rgba(*self._hsl_to_rgb(self.h, self.s, self.l), self.a, _validate=False)
|
|
342
|
+
return rgba(*self._hsl_to_rgb(self.h, self.s, self.l), self.a, _validate=False) # type: ignore[positional-arguments]
|
|
311
343
|
|
|
312
344
|
def to_hexa(self) -> "hexa":
|
|
313
345
|
"""Returns the color as a `hexa()` color"""
|
|
@@ -355,11 +387,11 @@ class hsla:
|
|
|
355
387
|
"""Inverts the color by rotating hue by 180 degrees and inverting lightness"""
|
|
356
388
|
self.h = (self.h + 180) % 360
|
|
357
389
|
self.l = 100 - self.l
|
|
358
|
-
if invert_alpha:
|
|
390
|
+
if invert_alpha and self.a is not None:
|
|
359
391
|
self.a = 1 - self.a
|
|
360
392
|
return hsla(self.h, self.s, self.l, self.a, _validate=False)
|
|
361
393
|
|
|
362
|
-
def grayscale(self, method:
|
|
394
|
+
def grayscale(self, method: Literal["wcag2", "wcag3", "simple", "bt601"] = "wcag2") -> "hsla":
|
|
363
395
|
"""Converts the color to grayscale using the luminance formula.\n
|
|
364
396
|
------------------------------------------------------------------
|
|
365
397
|
The `method` is the luminance calculation method to use:
|
|
@@ -367,11 +399,11 @@ class hsla:
|
|
|
367
399
|
- `"wcag3"` Draft WCAG 3.0 standard with improved coefficients
|
|
368
400
|
- `"simple"` Simple arithmetic mean (less accurate)
|
|
369
401
|
- `"bt601"` ITU-R BT.601 standard (older TV standard)"""
|
|
370
|
-
l = Color.luminance(*self._hsl_to_rgb(self.h, self.s, self.l), method=method)
|
|
402
|
+
l = int(Color.luminance(*self._hsl_to_rgb(self.h, self.s, self.l), method=method))
|
|
371
403
|
self.h, self.s, self.l, _ = rgba(l, l, l, _validate=False).to_hsla().values()
|
|
372
404
|
return hsla(self.h, self.s, self.l, self.a, _validate=False)
|
|
373
405
|
|
|
374
|
-
def blend(self, other:
|
|
406
|
+
def blend(self, other: Hsla, ratio: float = 0.5, additive_alpha: bool = False) -> "hsla":
|
|
375
407
|
"""Blends the current color with another color using the specified ratio (`0.0`-`1.0`):
|
|
376
408
|
- if `ratio` is `0.0` it means 100% of the current color and 0% of the `other` color (2:0 mixture)
|
|
377
409
|
- if `ratio` is `0.5` it means 50% of both colors (1:1 mixture)
|
|
@@ -406,9 +438,9 @@ class hsla:
|
|
|
406
438
|
return hsla((self.h + 180) % 360, self.s, self.l, self.a, _validate=False)
|
|
407
439
|
|
|
408
440
|
def _hsl_to_rgb(self, h: int, s: int, l: int) -> tuple:
|
|
409
|
-
|
|
410
|
-
if
|
|
411
|
-
r = g = b = int(
|
|
441
|
+
_h, _s, _l = h / 360, s / 100, l / 100
|
|
442
|
+
if _s == 0:
|
|
443
|
+
r = g = b = int(_l * 255)
|
|
412
444
|
else:
|
|
413
445
|
|
|
414
446
|
def hue_to_rgb(p, q, t):
|
|
@@ -424,11 +456,11 @@ class hsla:
|
|
|
424
456
|
return p + (q - p) * (2 / 3 - t) * 6
|
|
425
457
|
return p
|
|
426
458
|
|
|
427
|
-
q =
|
|
428
|
-
p = 2 *
|
|
429
|
-
r = int(round(hue_to_rgb(p, q,
|
|
430
|
-
g = int(round(hue_to_rgb(p, q,
|
|
431
|
-
b = int(round(hue_to_rgb(p, q,
|
|
459
|
+
q = _l * (1 + _s) if _l < 0.5 else _l + _s - _l * _s
|
|
460
|
+
p = 2 * _l - q
|
|
461
|
+
r = int(round(hue_to_rgb(p, q, _h + 1 / 3) * 255))
|
|
462
|
+
g = int(round(hue_to_rgb(p, q, _h) * 255))
|
|
463
|
+
b = int(round(hue_to_rgb(p, q, _h - 1 / 3) * 255))
|
|
432
464
|
return r, g, b
|
|
433
465
|
|
|
434
466
|
|
|
@@ -455,9 +487,20 @@ class hexa:
|
|
|
455
487
|
- `with_alpha(alpha)` to create a new color with different alpha
|
|
456
488
|
- `complementary()` to get the complementary color"""
|
|
457
489
|
|
|
458
|
-
def __init__(
|
|
490
|
+
def __init__(
|
|
491
|
+
self,
|
|
492
|
+
color: str | int,
|
|
493
|
+
_r: Optional[int] = None,
|
|
494
|
+
_g: Optional[int] = None,
|
|
495
|
+
_b: Optional[int] = None,
|
|
496
|
+
_a: Optional[float] = None,
|
|
497
|
+
):
|
|
498
|
+
self.r: int
|
|
499
|
+
self.g: int
|
|
500
|
+
self.b: int
|
|
501
|
+
self.a: Optional[float]
|
|
459
502
|
if all(x is not None for x in (_r, _g, _b)):
|
|
460
|
-
self.r, self.g, self.b, self.a = _r, _g, _b, _a
|
|
503
|
+
self.r, self.g, self.b, self.a = cast(int, _r), cast(int, _g), cast(int, _b), _a
|
|
461
504
|
return
|
|
462
505
|
if isinstance(color, hexa):
|
|
463
506
|
raise ValueError("Color is already a hexa() color")
|
|
@@ -504,14 +547,11 @@ class hexa:
|
|
|
504
547
|
def __len__(self) -> int:
|
|
505
548
|
return 3 if self.a is None else 4
|
|
506
549
|
|
|
507
|
-
def __iter__(self) ->
|
|
550
|
+
def __iter__(self) -> Iterator:
|
|
508
551
|
return iter((f"{self.r:02X}", f"{self.g:02X}", f"{self.b:02X}")
|
|
509
552
|
+ (() if self.a is None else (f"{int(self.a * 255):02X}", )))
|
|
510
553
|
|
|
511
|
-
def
|
|
512
|
-
return self.dict()
|
|
513
|
-
|
|
514
|
-
def __getitem__(self, index: int) -> int:
|
|
554
|
+
def __getitem__(self, index: int) -> str | int:
|
|
515
555
|
return ((f"{self.r:02X}", f"{self.g:02X}", f"{self.b:02X}") + (() if self.a is None else
|
|
516
556
|
(f"{int(self.a * 255):02X}", )))[index]
|
|
517
557
|
|
|
@@ -521,7 +561,7 @@ class hexa:
|
|
|
521
561
|
def __str__(self) -> str:
|
|
522
562
|
return f'#{self.r:02X}{self.g:02X}{self.b:02X}{"" if self.a is None else f"{int(self.a * 255):02X}"}'
|
|
523
563
|
|
|
524
|
-
def __eq__(self, other: "hexa") -> bool:
|
|
564
|
+
def __eq__(self, other: "hexa") -> bool: # type: ignore[override]
|
|
525
565
|
"""Returns whether the other color is equal to this one."""
|
|
526
566
|
if not isinstance(other, hexa):
|
|
527
567
|
return False
|
|
@@ -588,11 +628,11 @@ class hexa:
|
|
|
588
628
|
def invert(self, invert_alpha: bool = False) -> "hexa":
|
|
589
629
|
"""Inverts the color by rotating hue by 180 degrees and inverting lightness"""
|
|
590
630
|
self.r, self.g, self.b, self.a = self.to_rgba(False).invert().values()
|
|
591
|
-
if invert_alpha:
|
|
631
|
+
if invert_alpha and self.a is not None:
|
|
592
632
|
self.a = 1 - self.a
|
|
593
633
|
return hexa("", self.r, self.g, self.b, self.a)
|
|
594
634
|
|
|
595
|
-
def grayscale(self, method:
|
|
635
|
+
def grayscale(self, method: Literal["wcag2", "wcag3", "simple", "bt601"] = "wcag2") -> "hexa":
|
|
596
636
|
"""Converts the color to grayscale using the luminance formula.\n
|
|
597
637
|
------------------------------------------------------------------
|
|
598
638
|
The `method` is the luminance calculation method to use:
|
|
@@ -600,10 +640,10 @@ class hexa:
|
|
|
600
640
|
- `"wcag3"` Draft WCAG 3.0 standard with improved coefficients
|
|
601
641
|
- `"simple"` Simple arithmetic mean (less accurate)
|
|
602
642
|
- `"bt601"` ITU-R BT.601 standard (older TV standard)"""
|
|
603
|
-
self.r = self.g = self.b = Color.luminance(self.r, self.g, self.b, method=method)
|
|
643
|
+
self.r = self.g = self.b = int(Color.luminance(self.r, self.g, self.b, method=method))
|
|
604
644
|
return hexa("", self.r, self.g, self.b, self.a)
|
|
605
645
|
|
|
606
|
-
def blend(self, other:
|
|
646
|
+
def blend(self, other: Hexa, ratio: float = 0.5, additive_alpha: bool = False) -> "hexa":
|
|
607
647
|
"""Blends the current color with another color using the specified ratio (`0.0`-`1.0`):
|
|
608
648
|
- if `ratio` is `0.0` it means 100% of the current color and 0% of the `other` color (2:0 mixture)
|
|
609
649
|
- if `ratio` is `0.5` it means 50% of both colors (1:1 mixture)
|
|
@@ -641,7 +681,7 @@ class hexa:
|
|
|
641
681
|
class Color:
|
|
642
682
|
|
|
643
683
|
@staticmethod
|
|
644
|
-
def is_valid_rgba(color:
|
|
684
|
+
def is_valid_rgba(color: AnyRgba, allow_alpha: bool = True) -> bool:
|
|
645
685
|
try:
|
|
646
686
|
if isinstance(color, rgba):
|
|
647
687
|
return True
|
|
@@ -672,7 +712,7 @@ class Color:
|
|
|
672
712
|
return False
|
|
673
713
|
|
|
674
714
|
@staticmethod
|
|
675
|
-
def is_valid_hsla(color:
|
|
715
|
+
def is_valid_hsla(color: AnyHsla, allow_alpha: bool = True) -> bool:
|
|
676
716
|
try:
|
|
677
717
|
if isinstance(color, hsla):
|
|
678
718
|
return True
|
|
@@ -698,11 +738,16 @@ class Color:
|
|
|
698
738
|
return False
|
|
699
739
|
elif isinstance(color, str):
|
|
700
740
|
return bool(_re.fullmatch(Regex.hsla_str(allow_alpha=allow_alpha), color))
|
|
741
|
+
return False
|
|
701
742
|
except Exception:
|
|
702
743
|
return False
|
|
703
744
|
|
|
704
745
|
@staticmethod
|
|
705
|
-
def is_valid_hexa(
|
|
746
|
+
def is_valid_hexa(
|
|
747
|
+
color: AnyHexa,
|
|
748
|
+
allow_alpha: bool = True,
|
|
749
|
+
get_prefix: bool = False,
|
|
750
|
+
) -> bool | tuple[bool, Optional[Literal['#', '0x']]]:
|
|
706
751
|
try:
|
|
707
752
|
if isinstance(color, hexa):
|
|
708
753
|
return (True, "#") if get_prefix else True
|
|
@@ -714,18 +759,19 @@ class Color:
|
|
|
714
759
|
(color[2:], "0x") if color.startswith("0x") else (color, None))
|
|
715
760
|
return ((bool(_re.fullmatch(Regex.hexa_str(allow_alpha=allow_alpha), color)),
|
|
716
761
|
prefix) if get_prefix else bool(_re.fullmatch(Regex.hexa_str(allow_alpha=allow_alpha), color)))
|
|
762
|
+
return False
|
|
717
763
|
except Exception:
|
|
718
764
|
return (False, None) if get_prefix else False
|
|
719
765
|
|
|
720
766
|
@staticmethod
|
|
721
|
-
def is_valid(color:
|
|
767
|
+
def is_valid(color: AnyRgba | AnyHsla | AnyHexa, allow_alpha: bool = True) -> bool:
|
|
722
768
|
return bool(
|
|
723
769
|
Color.is_valid_rgba(color, allow_alpha) or Color.is_valid_hsla(color, allow_alpha)
|
|
724
770
|
or Color.is_valid_hexa(color, allow_alpha)
|
|
725
771
|
)
|
|
726
772
|
|
|
727
773
|
@staticmethod
|
|
728
|
-
def has_alpha(color:
|
|
774
|
+
def has_alpha(color: Rgba | Hsla | Hexa) -> bool:
|
|
729
775
|
"""Check if the given color has an alpha channel.\n
|
|
730
776
|
---------------------------------------------------------------------------
|
|
731
777
|
Input a RGBA, HSLA or HEXA color as `color`.
|
|
@@ -747,42 +793,42 @@ class Color:
|
|
|
747
793
|
return False
|
|
748
794
|
|
|
749
795
|
@staticmethod
|
|
750
|
-
def to_rgba(color:
|
|
796
|
+
def to_rgba(color: Rgba | Hsla | Hexa) -> rgba:
|
|
751
797
|
"""Will try to convert any color type to a color of type RGBA."""
|
|
752
798
|
if isinstance(color, (hsla, hexa)):
|
|
753
799
|
return color.to_rgba()
|
|
754
800
|
elif Color.is_valid_hsla(color):
|
|
755
|
-
return hsla(*color, _validate=False).to_rgba()
|
|
801
|
+
return hsla(*color, _validate=False).to_rgba() # type: ignore[not-iterable]
|
|
756
802
|
elif Color.is_valid_hexa(color):
|
|
757
|
-
return hexa(color).to_rgba()
|
|
803
|
+
return hexa(cast(str | int, color)).to_rgba()
|
|
758
804
|
elif Color.is_valid_rgba(color):
|
|
759
|
-
return color if isinstance(color, rgba) else (rgba(*color, _validate=False))
|
|
805
|
+
return color if isinstance(color, rgba) else (rgba(*color, _validate=False)) # type: ignore[not-iterable]
|
|
760
806
|
raise ValueError(f"Invalid color format '{color}'")
|
|
761
807
|
|
|
762
808
|
@staticmethod
|
|
763
|
-
def to_hsla(color:
|
|
809
|
+
def to_hsla(color: Rgba | Hsla | Hexa) -> hsla:
|
|
764
810
|
"""Will try to convert any color type to a color of type HSLA."""
|
|
765
811
|
if isinstance(color, (rgba, hexa)):
|
|
766
812
|
return color.to_hsla()
|
|
767
813
|
elif Color.is_valid_rgba(color):
|
|
768
|
-
return rgba(*color, _validate=False).to_hsla()
|
|
814
|
+
return rgba(*color, _validate=False).to_hsla() # type: ignore[not-iterable]
|
|
769
815
|
elif Color.is_valid_hexa(color):
|
|
770
|
-
return hexa(color).to_hsla()
|
|
816
|
+
return hexa(cast(str | int, color)).to_hsla()
|
|
771
817
|
elif Color.is_valid_hsla(color):
|
|
772
|
-
return color if isinstance(color, hsla) else (hsla(*color, _validate=False))
|
|
818
|
+
return color if isinstance(color, hsla) else (hsla(*color, _validate=False)) # type: ignore[not-iterable]
|
|
773
819
|
raise ValueError(f"Invalid color format '{color}'")
|
|
774
820
|
|
|
775
821
|
@staticmethod
|
|
776
|
-
def to_hexa(color:
|
|
822
|
+
def to_hexa(color: Rgba | Hsla | Hexa) -> hexa:
|
|
777
823
|
"""Will try to convert any color type to a color of type HEXA."""
|
|
778
824
|
if isinstance(color, (rgba, hsla)):
|
|
779
825
|
return color.to_hexa()
|
|
780
826
|
elif Color.is_valid_rgba(color):
|
|
781
|
-
return rgba(*color, _validate=False).to_hexa()
|
|
827
|
+
return rgba(*color, _validate=False).to_hexa() # type: ignore[not-iterable]
|
|
782
828
|
elif Color.is_valid_hsla(color):
|
|
783
|
-
return hsla(*color, _validate=False).to_hexa()
|
|
829
|
+
return hsla(*color, _validate=False).to_hexa() # type: ignore[not-iterable]
|
|
784
830
|
elif Color.is_valid_hexa(color):
|
|
785
|
-
return color if isinstance(color, hexa) else hexa(color)
|
|
831
|
+
return color if isinstance(color, hexa) else hexa(cast(str | int, color))
|
|
786
832
|
raise ValueError(f"Invalid color format '{color}'")
|
|
787
833
|
|
|
788
834
|
@staticmethod
|
|
@@ -821,7 +867,7 @@ class Color:
|
|
|
821
867
|
r: int,
|
|
822
868
|
g: int,
|
|
823
869
|
b: int,
|
|
824
|
-
a: float = None,
|
|
870
|
+
a: Optional[float] = None,
|
|
825
871
|
preserve_original: bool = False,
|
|
826
872
|
) -> int:
|
|
827
873
|
"""Convert RGBA channels to a HEXA integer (alpha is optional).\n
|
|
@@ -879,7 +925,13 @@ class Color:
|
|
|
879
925
|
raise ValueError(f"Invalid HEX integer '0x{hex_str}': expected in range [0x000000, 0xFFFFFF]")
|
|
880
926
|
|
|
881
927
|
@staticmethod
|
|
882
|
-
def luminance(
|
|
928
|
+
def luminance(
|
|
929
|
+
r: int,
|
|
930
|
+
g: int,
|
|
931
|
+
b: int,
|
|
932
|
+
output_type: Optional[type] = None,
|
|
933
|
+
method: Literal["wcag2", "wcag3", "simple", "bt601"] = "wcag2",
|
|
934
|
+
) -> int | float:
|
|
883
935
|
"""Calculates the relative luminance of a color according to various standards.\n
|
|
884
936
|
----------------------------------------------------------------------------------
|
|
885
937
|
The `output_type` controls the range of the returned luminance value:
|
|
@@ -891,21 +943,21 @@ class Color:
|
|
|
891
943
|
- `"wcag3"` Draft WCAG 3.0 standard with improved coefficients
|
|
892
944
|
- `"simple"` Simple arithmetic mean (less accurate)
|
|
893
945
|
- `"bt601"` ITU-R BT.601 standard (older TV standard)"""
|
|
894
|
-
|
|
946
|
+
_r, _g, _b = r / 255.0, g / 255.0, b / 255.0
|
|
895
947
|
if method == "simple":
|
|
896
|
-
luminance = (
|
|
948
|
+
luminance = (_r + _g + _b) / 3
|
|
897
949
|
elif method == "bt601":
|
|
898
|
-
luminance = 0.299 *
|
|
950
|
+
luminance = 0.299 * _r + 0.587 * _g + 0.114 * _b
|
|
899
951
|
elif method == "wcag3":
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
luminance = 0.2126729 *
|
|
952
|
+
_r = Color._linearize_srgb(_r)
|
|
953
|
+
_g = Color._linearize_srgb(_g)
|
|
954
|
+
_b = Color._linearize_srgb(_b)
|
|
955
|
+
luminance = 0.2126729 * _r + 0.7151522 * _g + 0.0721750 * _b
|
|
904
956
|
else:
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
luminance = 0.2126 *
|
|
957
|
+
_r = Color._linearize_srgb(_r)
|
|
958
|
+
_g = Color._linearize_srgb(_g)
|
|
959
|
+
_b = Color._linearize_srgb(_b)
|
|
960
|
+
luminance = 0.2126 * _r + 0.7152 * _g + 0.0722 * _b
|
|
909
961
|
if output_type == int:
|
|
910
962
|
return round(luminance * 100)
|
|
911
963
|
elif output_type == float:
|
|
@@ -922,15 +974,16 @@ class Color:
|
|
|
922
974
|
return ((c + 0.055) / 1.055)**2.4
|
|
923
975
|
|
|
924
976
|
@staticmethod
|
|
925
|
-
def text_color_for_on_bg(text_bg_color:
|
|
977
|
+
def text_color_for_on_bg(text_bg_color: Rgba | Hexa) -> rgba | hexa | int:
|
|
926
978
|
was_hexa, was_int = Color.is_valid_hexa(text_bg_color), isinstance(text_bg_color, int)
|
|
927
979
|
text_bg_color = Color.to_rgba(text_bg_color)
|
|
928
980
|
brightness = 0.2126 * text_bg_color[0] + 0.7152 * text_bg_color[1] + 0.0722 * text_bg_color[2]
|
|
929
|
-
return ((hexa("", 255, 255, 255) if was_hexa else rgba(255, 255, 255, _validate=False))
|
|
981
|
+
return (((0xFFFFFF if was_int else hexa("", 255, 255, 255)) if was_hexa else rgba(255, 255, 255, _validate=False))
|
|
982
|
+
if brightness < 128 else
|
|
930
983
|
((0x000 if was_int else hexa("", 0, 0, 0)) if was_hexa else rgba(0, 0, 0, _validate=False)))
|
|
931
984
|
|
|
932
985
|
@staticmethod
|
|
933
|
-
def adjust_lightness(color:
|
|
986
|
+
def adjust_lightness(color: Rgba | Hexa, lightness_change: float) -> rgba | hexa:
|
|
934
987
|
"""In- or decrease the lightness of the input color.\n
|
|
935
988
|
-----------------------------------------------------------------------------------------------------
|
|
936
989
|
- color (rgba|hexa): HEX or RGBA color
|
|
@@ -938,18 +991,13 @@ class Color:
|
|
|
938
991
|
-----------------------------------------------------------------------------------------------------
|
|
939
992
|
returns (rgba|hexa): the adjusted color in the format of the input color"""
|
|
940
993
|
was_hexa = Color.is_valid_hexa(color)
|
|
941
|
-
|
|
942
|
-
h, s, l, a = (
|
|
943
|
-
color[0],
|
|
944
|
-
color[1],
|
|
945
|
-
color[2],
|
|
946
|
-
color[3] if Color.has_alpha(color) else None,
|
|
947
|
-
)
|
|
994
|
+
_color: hsla = Color.to_hsla(color)
|
|
995
|
+
h, s, l, a = (int(_color[0]), int(_color[1]), int(_color[2]), _color[3] if Color.has_alpha(_color) else None)
|
|
948
996
|
l = int(max(0, min(100, l + lightness_change * 100)))
|
|
949
997
|
return hsla(h, s, l, a, _validate=False).to_hexa() if was_hexa else hsla(h, s, l, a, _validate=False).to_rgba()
|
|
950
998
|
|
|
951
999
|
@staticmethod
|
|
952
|
-
def adjust_saturation(color:
|
|
1000
|
+
def adjust_saturation(color: Rgba | Hexa, saturation_change: float) -> rgba | hexa:
|
|
953
1001
|
"""In- or decrease the saturation of the input color.\n
|
|
954
1002
|
-----------------------------------------------------------------------------------------------------------
|
|
955
1003
|
- color (rgba|hexa): HEX or RGBA color
|
|
@@ -957,12 +1005,7 @@ class Color:
|
|
|
957
1005
|
-----------------------------------------------------------------------------------------------------------
|
|
958
1006
|
returns (rgba|hexa): the adjusted color in the format of the input color"""
|
|
959
1007
|
was_hexa = Color.is_valid_hexa(color)
|
|
960
|
-
|
|
961
|
-
h, s, l, a = (
|
|
962
|
-
color[0],
|
|
963
|
-
color[1],
|
|
964
|
-
color[2],
|
|
965
|
-
color[3] if Color.has_alpha(color) else None,
|
|
966
|
-
)
|
|
1008
|
+
_color: hsla = Color.to_hsla(color)
|
|
1009
|
+
h, s, l, a = (int(_color[0]), int(_color[1]), int(_color[2]), _color[3] if Color.has_alpha(_color) else None)
|
|
967
1010
|
s = int(max(0, min(100, s + saturation_change * 100)))
|
|
968
1011
|
return hsla(h, s, l, a, _validate=False).to_hexa() if was_hexa else hsla(h, s, l, a, _validate=False).to_rgba()
|