xulbux 1.6.9__py3-none-any.whl → 1.7.0__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 +146 -115
- xulbux/xx_console.py +55 -40
- xulbux/xx_data.py +22 -21
- xulbux/xx_env_path.py +6 -5
- xulbux/xx_file.py +2 -2
- xulbux/xx_format_codes.py +30 -25
- xulbux/xx_json.py +7 -6
- xulbux/xx_path.py +7 -7
- xulbux/xx_regex.py +6 -1
- xulbux/xx_system.py +5 -5
- {xulbux-1.6.9.dist-info → xulbux-1.7.0.dist-info}/METADATA +1 -1
- xulbux-1.7.0.dist-info/RECORD +21 -0
- {xulbux-1.6.9.dist-info → xulbux-1.7.0.dist-info}/WHEEL +1 -1
- xulbux-1.6.9.dist-info/RECORD +0 -21
- {xulbux-1.6.9.dist-info → xulbux-1.7.0.dist-info}/entry_points.txt +0 -0
- {xulbux-1.6.9.dist-info → xulbux-1.7.0.dist-info}/licenses/LICENSE +0 -0
- {xulbux-1.6.9.dist-info → xulbux-1.7.0.dist-info}/top_level.txt +0 -0
xulbux/xx_color.py
CHANGED
|
@@ -33,10 +33,36 @@ 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
|
|
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
|
+
Rgba: TypeAlias = Union[
|
|
46
|
+
tuple[Int_0_255, Int_0_255, Int_0_255],
|
|
47
|
+
tuple[Int_0_255, Int_0_255, Int_0_255, Float_0_1],
|
|
48
|
+
list[Int_0_255],
|
|
49
|
+
list[Union[Int_0_255, Float_0_1]],
|
|
50
|
+
dict[str, Union[int, float]],
|
|
51
|
+
"rgba",
|
|
52
|
+
str,
|
|
53
|
+
]
|
|
54
|
+
Hsla: TypeAlias = Union[
|
|
55
|
+
tuple[Int_0_360, Int_0_100, Int_0_100],
|
|
56
|
+
tuple[Int_0_360, Int_0_100, Int_0_100, Float_0_1],
|
|
57
|
+
list[Union[Int_0_360, Int_0_100]],
|
|
58
|
+
list[Union[Int_0_360, Int_0_100, Float_0_1]],
|
|
59
|
+
dict[str, Union[int, float]],
|
|
60
|
+
"hsla",
|
|
61
|
+
str,
|
|
62
|
+
]
|
|
63
|
+
Hexa: TypeAlias = Union[str, int, "hexa"]
|
|
64
|
+
|
|
65
|
+
|
|
40
66
|
class rgba:
|
|
41
67
|
"""An RGB/RGBA color: is a tuple of 3 integers, representing the red (`0`-`255`), green (`0`-`255`), and blue (`0`-`255`).\n
|
|
42
68
|
Also includes an optional 4th param, which is a float, that represents the alpha channel (`0.0`-`1.0`).\n
|
|
@@ -60,7 +86,11 @@ class rgba:
|
|
|
60
86
|
- `with_alpha(alpha)` to create a new color with different alpha
|
|
61
87
|
- `complementary()` to get the complementary color"""
|
|
62
88
|
|
|
63
|
-
def __init__(self, r: int, g: int, b: int, a: float = None, _validate: bool = True):
|
|
89
|
+
def __init__(self, r: int, g: int, b: int, a: Optional[float] = None, _validate: bool = True):
|
|
90
|
+
self.r: int
|
|
91
|
+
self.g: int
|
|
92
|
+
self.b: int
|
|
93
|
+
self.a: Optional[float]
|
|
64
94
|
if not _validate:
|
|
65
95
|
self.r, self.g, self.b, self.a = r, g, b, a
|
|
66
96
|
return
|
|
@@ -79,13 +109,10 @@ class rgba:
|
|
|
79
109
|
def __len__(self) -> int:
|
|
80
110
|
return 3 if self.a is None else 4
|
|
81
111
|
|
|
82
|
-
def __iter__(self) ->
|
|
112
|
+
def __iter__(self) -> Iterator:
|
|
83
113
|
return iter((self.r, self.g, self.b) + (() if self.a is None else (self.a, )))
|
|
84
114
|
|
|
85
|
-
def
|
|
86
|
-
return self.dict()
|
|
87
|
-
|
|
88
|
-
def __getitem__(self, index: int) -> int:
|
|
115
|
+
def __getitem__(self, index: int) -> int | float:
|
|
89
116
|
return ((self.r, self.g, self.b) + (() if self.a is None else (self.a, )))[index]
|
|
90
117
|
|
|
91
118
|
def __repr__(self) -> str:
|
|
@@ -94,7 +121,7 @@ class rgba:
|
|
|
94
121
|
def __str__(self) -> str:
|
|
95
122
|
return f'({self.r}, {self.g}, {self.b}{"" if self.a is None else f", {self.a}"})'
|
|
96
123
|
|
|
97
|
-
def __eq__(self, other: "rgba") -> bool:
|
|
124
|
+
def __eq__(self, other: "rgba") -> bool: # type: ignore[override]
|
|
98
125
|
if not isinstance(other, rgba):
|
|
99
126
|
return False
|
|
100
127
|
return (self.r, self.g, self.b, self.a) == (other.r, other.g, other.b, other.a)
|
|
@@ -109,7 +136,7 @@ class rgba:
|
|
|
109
136
|
|
|
110
137
|
def to_hsla(self) -> "hsla":
|
|
111
138
|
"""Returns the color as a `hsla()` color"""
|
|
112
|
-
return hsla(*self._rgb_to_hsl(self.r, self.g, self.b), self.a, _validate=False)
|
|
139
|
+
return hsla(*self._rgb_to_hsl(self.r, self.g, self.b), self.a, _validate=False) # type: ignore[positional-arguments]
|
|
113
140
|
|
|
114
141
|
def to_hexa(self) -> "hexa":
|
|
115
142
|
"""Returns the color as a `hexa()` color"""
|
|
@@ -147,7 +174,7 @@ class rgba:
|
|
|
147
174
|
def invert(self, invert_alpha: bool = False) -> "rgba":
|
|
148
175
|
"""Inverts the color by rotating hue by 180 degrees and inverting lightness"""
|
|
149
176
|
self.r, self.g, self.b = 255 - self.r, 255 - self.g, 255 - self.b
|
|
150
|
-
if invert_alpha:
|
|
177
|
+
if invert_alpha and self.a is not None:
|
|
151
178
|
self.a = 1 - self.a
|
|
152
179
|
return rgba(self.r, self.g, self.b, self.a, _validate=False)
|
|
153
180
|
|
|
@@ -159,10 +186,10 @@ class rgba:
|
|
|
159
186
|
- `"wcag3"` Draft WCAG 3.0 standard with improved coefficients
|
|
160
187
|
- `"simple"` Simple arithmetic mean (less accurate)
|
|
161
188
|
- `"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)
|
|
189
|
+
self.r = self.g = self.b = int(Color.luminance(self.r, self.g, self.b, method=method))
|
|
163
190
|
return rgba(self.r, self.g, self.b, self.a, _validate=False)
|
|
164
191
|
|
|
165
|
-
def blend(self, other:
|
|
192
|
+
def blend(self, other: Rgba, ratio: float = 0.5, additive_alpha: bool = False) -> "rgba":
|
|
166
193
|
"""Blends the current color with another color using the specified ratio (`0.0`-`1.0`):
|
|
167
194
|
- if `ratio` is `0.0` it means 100% of the current color and 0% of the `other` color (2:0 mixture)
|
|
168
195
|
- if `ratio` is `0.5` it means 50% of both colors (1:1 mixture)
|
|
@@ -171,7 +198,7 @@ class rgba:
|
|
|
171
198
|
raise ValueError("'ratio' must be a float/int in [0.0, 1.0]")
|
|
172
199
|
elif not isinstance(other, rgba):
|
|
173
200
|
if Color.is_valid_rgba(other):
|
|
174
|
-
other =
|
|
201
|
+
other = Color.to_rgba(other)
|
|
175
202
|
else:
|
|
176
203
|
raise TypeError("'other' must be a valid RGBA color")
|
|
177
204
|
ratio *= 2
|
|
@@ -217,20 +244,20 @@ class rgba:
|
|
|
217
244
|
return self.to_hsla().complementary().to_rgba()
|
|
218
245
|
|
|
219
246
|
def _rgb_to_hsl(self, r: int, g: int, b: int) -> tuple:
|
|
220
|
-
|
|
221
|
-
max_c, min_c = max(
|
|
247
|
+
_r, _g, _b = r / 255.0, g / 255.0, b / 255.0
|
|
248
|
+
max_c, min_c = max(_r, _g, _b), min(_r, _g, _b)
|
|
222
249
|
l = (max_c + min_c) / 2
|
|
223
250
|
if max_c == min_c:
|
|
224
251
|
h = s = 0
|
|
225
252
|
else:
|
|
226
253
|
delta = max_c - min_c
|
|
227
254
|
s = delta / (1 - abs(2 * l - 1))
|
|
228
|
-
if max_c ==
|
|
229
|
-
h = ((
|
|
230
|
-
elif max_c ==
|
|
231
|
-
h = ((
|
|
255
|
+
if max_c == _r:
|
|
256
|
+
h = ((_g - _b) / delta) % 6
|
|
257
|
+
elif max_c == _g:
|
|
258
|
+
h = ((_b - _r) / delta) + 2
|
|
232
259
|
else:
|
|
233
|
-
h = ((
|
|
260
|
+
h = ((_r - _g) / delta) + 4
|
|
234
261
|
h /= 6
|
|
235
262
|
return int(round(h * 360)), int(round(s * 100)), int(round(l * 100))
|
|
236
263
|
|
|
@@ -258,7 +285,11 @@ class hsla:
|
|
|
258
285
|
- `with_alpha(alpha)` to create a new color with different alpha
|
|
259
286
|
- `complementary()` to get the complementary color"""
|
|
260
287
|
|
|
261
|
-
def __init__(self, h: int, s: int, l: int, a: float = None, _validate: bool = True):
|
|
288
|
+
def __init__(self, h: int, s: int, l: int, a: Optional[float] = None, _validate: bool = True):
|
|
289
|
+
self.h: int
|
|
290
|
+
self.s: int
|
|
291
|
+
self.l: int
|
|
292
|
+
self.a: Optional[float]
|
|
262
293
|
if not _validate:
|
|
263
294
|
self.h, self.s, self.l, self.a = h, s, l, a
|
|
264
295
|
return
|
|
@@ -277,13 +308,10 @@ class hsla:
|
|
|
277
308
|
def __len__(self) -> int:
|
|
278
309
|
return 3 if self.a is None else 4
|
|
279
310
|
|
|
280
|
-
def __iter__(self) ->
|
|
311
|
+
def __iter__(self) -> Iterator:
|
|
281
312
|
return iter((self.h, self.s, self.l) + (() if self.a is None else (self.a, )))
|
|
282
313
|
|
|
283
|
-
def
|
|
284
|
-
return self.dict()
|
|
285
|
-
|
|
286
|
-
def __getitem__(self, index: int) -> int:
|
|
314
|
+
def __getitem__(self, index: int) -> int | float:
|
|
287
315
|
return ((self.h, self.s, self.l) + (() if self.a is None else (self.a, )))[index]
|
|
288
316
|
|
|
289
317
|
def __repr__(self) -> str:
|
|
@@ -292,7 +320,7 @@ class hsla:
|
|
|
292
320
|
def __str__(self) -> str:
|
|
293
321
|
return f'({self.h}°, {self.s}%, {self.l}%{"" if self.a is None else f", {self.a}"})'
|
|
294
322
|
|
|
295
|
-
def __eq__(self, other: "hsla") -> bool:
|
|
323
|
+
def __eq__(self, other: "hsla") -> bool: # type: ignore[override]
|
|
296
324
|
if not isinstance(other, hsla):
|
|
297
325
|
return False
|
|
298
326
|
return (self.h, self.s, self.l, self.a) == (other.h, other.s, other.l, other.a)
|
|
@@ -307,7 +335,7 @@ class hsla:
|
|
|
307
335
|
|
|
308
336
|
def to_rgba(self) -> "rgba":
|
|
309
337
|
"""Returns the color as a `rgba()` color"""
|
|
310
|
-
return rgba(*self._hsl_to_rgb(self.h, self.s, self.l), self.a, _validate=False)
|
|
338
|
+
return rgba(*self._hsl_to_rgb(self.h, self.s, self.l), self.a, _validate=False) # type: ignore[positional-arguments]
|
|
311
339
|
|
|
312
340
|
def to_hexa(self) -> "hexa":
|
|
313
341
|
"""Returns the color as a `hexa()` color"""
|
|
@@ -355,7 +383,7 @@ class hsla:
|
|
|
355
383
|
"""Inverts the color by rotating hue by 180 degrees and inverting lightness"""
|
|
356
384
|
self.h = (self.h + 180) % 360
|
|
357
385
|
self.l = 100 - self.l
|
|
358
|
-
if invert_alpha:
|
|
386
|
+
if invert_alpha and self.a is not None:
|
|
359
387
|
self.a = 1 - self.a
|
|
360
388
|
return hsla(self.h, self.s, self.l, self.a, _validate=False)
|
|
361
389
|
|
|
@@ -367,11 +395,11 @@ class hsla:
|
|
|
367
395
|
- `"wcag3"` Draft WCAG 3.0 standard with improved coefficients
|
|
368
396
|
- `"simple"` Simple arithmetic mean (less accurate)
|
|
369
397
|
- `"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)
|
|
398
|
+
l = int(Color.luminance(*self._hsl_to_rgb(self.h, self.s, self.l), method=method))
|
|
371
399
|
self.h, self.s, self.l, _ = rgba(l, l, l, _validate=False).to_hsla().values()
|
|
372
400
|
return hsla(self.h, self.s, self.l, self.a, _validate=False)
|
|
373
401
|
|
|
374
|
-
def blend(self, other:
|
|
402
|
+
def blend(self, other: Hsla, ratio: float = 0.5, additive_alpha: bool = False) -> "hsla":
|
|
375
403
|
"""Blends the current color with another color using the specified ratio (`0.0`-`1.0`):
|
|
376
404
|
- if `ratio` is `0.0` it means 100% of the current color and 0% of the `other` color (2:0 mixture)
|
|
377
405
|
- if `ratio` is `0.5` it means 50% of both colors (1:1 mixture)
|
|
@@ -406,9 +434,9 @@ class hsla:
|
|
|
406
434
|
return hsla((self.h + 180) % 360, self.s, self.l, self.a, _validate=False)
|
|
407
435
|
|
|
408
436
|
def _hsl_to_rgb(self, h: int, s: int, l: int) -> tuple:
|
|
409
|
-
|
|
410
|
-
if
|
|
411
|
-
r = g = b = int(
|
|
437
|
+
_h, _s, _l = h / 360, s / 100, l / 100
|
|
438
|
+
if _s == 0:
|
|
439
|
+
r = g = b = int(_l * 255)
|
|
412
440
|
else:
|
|
413
441
|
|
|
414
442
|
def hue_to_rgb(p, q, t):
|
|
@@ -424,11 +452,11 @@ class hsla:
|
|
|
424
452
|
return p + (q - p) * (2 / 3 - t) * 6
|
|
425
453
|
return p
|
|
426
454
|
|
|
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,
|
|
455
|
+
q = _l * (1 + _s) if _l < 0.5 else _l + _s - _l * _s
|
|
456
|
+
p = 2 * _l - q
|
|
457
|
+
r = int(round(hue_to_rgb(p, q, _h + 1 / 3) * 255))
|
|
458
|
+
g = int(round(hue_to_rgb(p, q, _h) * 255))
|
|
459
|
+
b = int(round(hue_to_rgb(p, q, _h - 1 / 3) * 255))
|
|
432
460
|
return r, g, b
|
|
433
461
|
|
|
434
462
|
|
|
@@ -455,9 +483,20 @@ class hexa:
|
|
|
455
483
|
- `with_alpha(alpha)` to create a new color with different alpha
|
|
456
484
|
- `complementary()` to get the complementary color"""
|
|
457
485
|
|
|
458
|
-
def __init__(
|
|
486
|
+
def __init__(
|
|
487
|
+
self,
|
|
488
|
+
color: str | int,
|
|
489
|
+
_r: Optional[int] = None,
|
|
490
|
+
_g: Optional[int] = None,
|
|
491
|
+
_b: Optional[int] = None,
|
|
492
|
+
_a: Optional[float] = None,
|
|
493
|
+
):
|
|
494
|
+
self.r: int
|
|
495
|
+
self.g: int
|
|
496
|
+
self.b: int
|
|
497
|
+
self.a: Optional[float]
|
|
459
498
|
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
|
|
499
|
+
self.r, self.g, self.b, self.a = _r, _g, _b, _a # type: ignore[assignment]
|
|
461
500
|
return
|
|
462
501
|
if isinstance(color, hexa):
|
|
463
502
|
raise ValueError("Color is already a hexa() color")
|
|
@@ -504,14 +543,11 @@ class hexa:
|
|
|
504
543
|
def __len__(self) -> int:
|
|
505
544
|
return 3 if self.a is None else 4
|
|
506
545
|
|
|
507
|
-
def __iter__(self) ->
|
|
546
|
+
def __iter__(self) -> Iterator:
|
|
508
547
|
return iter((f"{self.r:02X}", f"{self.g:02X}", f"{self.b:02X}")
|
|
509
548
|
+ (() if self.a is None else (f"{int(self.a * 255):02X}", )))
|
|
510
549
|
|
|
511
|
-
def
|
|
512
|
-
return self.dict()
|
|
513
|
-
|
|
514
|
-
def __getitem__(self, index: int) -> int:
|
|
550
|
+
def __getitem__(self, index: int) -> str | int:
|
|
515
551
|
return ((f"{self.r:02X}", f"{self.g:02X}", f"{self.b:02X}") + (() if self.a is None else
|
|
516
552
|
(f"{int(self.a * 255):02X}", )))[index]
|
|
517
553
|
|
|
@@ -521,7 +557,7 @@ class hexa:
|
|
|
521
557
|
def __str__(self) -> str:
|
|
522
558
|
return f'#{self.r:02X}{self.g:02X}{self.b:02X}{"" if self.a is None else f"{int(self.a * 255):02X}"}'
|
|
523
559
|
|
|
524
|
-
def __eq__(self, other: "hexa") -> bool:
|
|
560
|
+
def __eq__(self, other: "hexa") -> bool: # type: ignore[override]
|
|
525
561
|
"""Returns whether the other color is equal to this one."""
|
|
526
562
|
if not isinstance(other, hexa):
|
|
527
563
|
return False
|
|
@@ -588,7 +624,7 @@ class hexa:
|
|
|
588
624
|
def invert(self, invert_alpha: bool = False) -> "hexa":
|
|
589
625
|
"""Inverts the color by rotating hue by 180 degrees and inverting lightness"""
|
|
590
626
|
self.r, self.g, self.b, self.a = self.to_rgba(False).invert().values()
|
|
591
|
-
if invert_alpha:
|
|
627
|
+
if invert_alpha and self.a is not None:
|
|
592
628
|
self.a = 1 - self.a
|
|
593
629
|
return hexa("", self.r, self.g, self.b, self.a)
|
|
594
630
|
|
|
@@ -600,10 +636,10 @@ class hexa:
|
|
|
600
636
|
- `"wcag3"` Draft WCAG 3.0 standard with improved coefficients
|
|
601
637
|
- `"simple"` Simple arithmetic mean (less accurate)
|
|
602
638
|
- `"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)
|
|
639
|
+
self.r = self.g = self.b = int(Color.luminance(self.r, self.g, self.b, method=method))
|
|
604
640
|
return hexa("", self.r, self.g, self.b, self.a)
|
|
605
641
|
|
|
606
|
-
def blend(self, other:
|
|
642
|
+
def blend(self, other: Hexa, ratio: float = 0.5, additive_alpha: bool = False) -> "hexa":
|
|
607
643
|
"""Blends the current color with another color using the specified ratio (`0.0`-`1.0`):
|
|
608
644
|
- if `ratio` is `0.0` it means 100% of the current color and 0% of the `other` color (2:0 mixture)
|
|
609
645
|
- if `ratio` is `0.5` it means 50% of both colors (1:1 mixture)
|
|
@@ -641,7 +677,7 @@ class hexa:
|
|
|
641
677
|
class Color:
|
|
642
678
|
|
|
643
679
|
@staticmethod
|
|
644
|
-
def is_valid_rgba(color:
|
|
680
|
+
def is_valid_rgba(color: Rgba, allow_alpha: bool = True) -> bool:
|
|
645
681
|
try:
|
|
646
682
|
if isinstance(color, rgba):
|
|
647
683
|
return True
|
|
@@ -649,7 +685,7 @@ class Color:
|
|
|
649
685
|
if allow_alpha and Color.has_alpha(color):
|
|
650
686
|
return (
|
|
651
687
|
0 <= color[0] <= 255 and 0 <= color[1] <= 255 and 0 <= color[2] <= 255
|
|
652
|
-
and (0 <= color[3] <= 1 or color[3] is None)
|
|
688
|
+
and (0 <= color[3] <= 1 or color[3] is None) # type: ignore[index]
|
|
653
689
|
)
|
|
654
690
|
elif len(color) == 3:
|
|
655
691
|
return 0 <= color[0] <= 255 and 0 <= color[1] <= 255 and 0 <= color[2] <= 255
|
|
@@ -672,7 +708,7 @@ class Color:
|
|
|
672
708
|
return False
|
|
673
709
|
|
|
674
710
|
@staticmethod
|
|
675
|
-
def is_valid_hsla(color:
|
|
711
|
+
def is_valid_hsla(color: Hsla, allow_alpha: bool = True) -> bool:
|
|
676
712
|
try:
|
|
677
713
|
if isinstance(color, hsla):
|
|
678
714
|
return True
|
|
@@ -680,7 +716,7 @@ class Color:
|
|
|
680
716
|
if allow_alpha and Color.has_alpha(color):
|
|
681
717
|
return (
|
|
682
718
|
0 <= color[0] <= 360 and 0 <= color[1] <= 100 and 0 <= color[2] <= 100
|
|
683
|
-
and (0 <= color[3] <= 1 or color[3] is None)
|
|
719
|
+
and (0 <= color[3] <= 1 or color[3] is None) # type: ignore[index]
|
|
684
720
|
)
|
|
685
721
|
elif len(color) == 3:
|
|
686
722
|
return 0 <= color[0] <= 360 and 0 <= color[1] <= 100 and 0 <= color[2] <= 100
|
|
@@ -702,7 +738,11 @@ class Color:
|
|
|
702
738
|
return False
|
|
703
739
|
|
|
704
740
|
@staticmethod
|
|
705
|
-
def is_valid_hexa(
|
|
741
|
+
def is_valid_hexa(
|
|
742
|
+
color: Hexa,
|
|
743
|
+
allow_alpha: bool = True,
|
|
744
|
+
get_prefix: bool = False,
|
|
745
|
+
) -> bool | tuple[bool, Optional[Literal['#', '0x']]]:
|
|
706
746
|
try:
|
|
707
747
|
if isinstance(color, hexa):
|
|
708
748
|
return (True, "#") if get_prefix else True
|
|
@@ -718,21 +758,21 @@ class Color:
|
|
|
718
758
|
return (False, None) if get_prefix else False
|
|
719
759
|
|
|
720
760
|
@staticmethod
|
|
721
|
-
def is_valid(color:
|
|
761
|
+
def is_valid(color: Rgba | Hsla | Hexa, allow_alpha: bool = True) -> bool:
|
|
722
762
|
return bool(
|
|
723
|
-
Color.is_valid_rgba(color, allow_alpha) or Color.is_valid_hsla(color, allow_alpha)
|
|
724
|
-
or Color.is_valid_hexa(color, allow_alpha)
|
|
763
|
+
Color.is_valid_rgba(color, allow_alpha) or Color.is_valid_hsla(color, allow_alpha) # type: ignore[assignment]
|
|
764
|
+
or Color.is_valid_hexa(color, allow_alpha) # type: ignore[assignment]
|
|
725
765
|
)
|
|
726
766
|
|
|
727
767
|
@staticmethod
|
|
728
|
-
def has_alpha(color:
|
|
768
|
+
def has_alpha(color: Rgba | Hsla | Hexa) -> bool:
|
|
729
769
|
"""Check if the given color has an alpha channel.\n
|
|
730
770
|
---------------------------------------------------------------------------
|
|
731
771
|
Input a RGBA, HSLA or HEXA color as `color`.
|
|
732
772
|
Returns `True` if the color has an alpha channel and `False` otherwise."""
|
|
733
773
|
if isinstance(color, (rgba, hsla, hexa)):
|
|
734
774
|
return color.has_alpha()
|
|
735
|
-
if Color.is_valid_hexa(color):
|
|
775
|
+
if Color.is_valid_hexa(color): # type: ignore[assignment]
|
|
736
776
|
if isinstance(color, str):
|
|
737
777
|
if color.startswith("#"):
|
|
738
778
|
color = color[1:]
|
|
@@ -747,42 +787,42 @@ class Color:
|
|
|
747
787
|
return False
|
|
748
788
|
|
|
749
789
|
@staticmethod
|
|
750
|
-
def to_rgba(color:
|
|
790
|
+
def to_rgba(color: Rgba | Hsla | Hexa) -> rgba:
|
|
751
791
|
"""Will try to convert any color type to a color of type RGBA."""
|
|
752
792
|
if isinstance(color, (hsla, hexa)):
|
|
753
793
|
return color.to_rgba()
|
|
754
|
-
elif Color.is_valid_hsla(color):
|
|
755
|
-
return hsla(*color, _validate=False).to_rgba()
|
|
756
|
-
elif Color.is_valid_hexa(color):
|
|
757
|
-
return hexa(color).to_rgba()
|
|
758
|
-
elif Color.is_valid_rgba(color):
|
|
759
|
-
return color if isinstance(color, rgba) else (rgba(*color, _validate=False))
|
|
794
|
+
elif Color.is_valid_hsla(color): # type: ignore[assignment]
|
|
795
|
+
return hsla(*color, _validate=False).to_rgba() # type: ignore[not-iterable]
|
|
796
|
+
elif Color.is_valid_hexa(color): # type: ignore[assignment]
|
|
797
|
+
return hexa(color).to_rgba() # type: ignore[assignment]
|
|
798
|
+
elif Color.is_valid_rgba(color): # type: ignore[assignment]
|
|
799
|
+
return color if isinstance(color, rgba) else (rgba(*color, _validate=False)) # type: ignore[not-iterable]
|
|
760
800
|
raise ValueError(f"Invalid color format '{color}'")
|
|
761
801
|
|
|
762
802
|
@staticmethod
|
|
763
|
-
def to_hsla(color:
|
|
803
|
+
def to_hsla(color: Rgba | Hsla | Hexa) -> hsla:
|
|
764
804
|
"""Will try to convert any color type to a color of type HSLA."""
|
|
765
805
|
if isinstance(color, (rgba, hexa)):
|
|
766
806
|
return color.to_hsla()
|
|
767
|
-
elif Color.is_valid_rgba(color):
|
|
768
|
-
return rgba(*color, _validate=False).to_hsla()
|
|
769
|
-
elif Color.is_valid_hexa(color):
|
|
770
|
-
return hexa(color).to_hsla()
|
|
771
|
-
elif Color.is_valid_hsla(color):
|
|
772
|
-
return color if isinstance(color, hsla) else (hsla(*color, _validate=False))
|
|
807
|
+
elif Color.is_valid_rgba(color): # type: ignore[assignment]
|
|
808
|
+
return rgba(*color, _validate=False).to_hsla() # type: ignore[not-iterable]
|
|
809
|
+
elif Color.is_valid_hexa(color): # type: ignore[assignment]
|
|
810
|
+
return hexa(color).to_hsla() # type: ignore[assignment]
|
|
811
|
+
elif Color.is_valid_hsla(color): # type: ignore[assignment]
|
|
812
|
+
return color if isinstance(color, hsla) else (hsla(*color, _validate=False)) # type: ignore[not-iterable]
|
|
773
813
|
raise ValueError(f"Invalid color format '{color}'")
|
|
774
814
|
|
|
775
815
|
@staticmethod
|
|
776
|
-
def to_hexa(color:
|
|
816
|
+
def to_hexa(color: Rgba | Hsla | Hexa) -> hexa:
|
|
777
817
|
"""Will try to convert any color type to a color of type HEXA."""
|
|
778
818
|
if isinstance(color, (rgba, hsla)):
|
|
779
819
|
return color.to_hexa()
|
|
780
|
-
elif Color.is_valid_rgba(color):
|
|
781
|
-
return rgba(*color, _validate=False).to_hexa()
|
|
782
|
-
elif Color.is_valid_hsla(color):
|
|
783
|
-
return hsla(*color, _validate=False).to_hexa()
|
|
784
|
-
elif Color.is_valid_hexa(color):
|
|
785
|
-
return color if isinstance(color, hexa) else hexa(color)
|
|
820
|
+
elif Color.is_valid_rgba(color): # type: ignore[assignment]
|
|
821
|
+
return rgba(*color, _validate=False).to_hexa() # type: ignore[not-iterable]
|
|
822
|
+
elif Color.is_valid_hsla(color): # type: ignore[assignment]
|
|
823
|
+
return hsla(*color, _validate=False).to_hexa() # type: ignore[not-iterable]
|
|
824
|
+
elif Color.is_valid_hexa(color): # type: ignore[assignment]
|
|
825
|
+
return color if isinstance(color, hexa) else hexa(color) # type: ignore[assignment]
|
|
786
826
|
raise ValueError(f"Invalid color format '{color}'")
|
|
787
827
|
|
|
788
828
|
@staticmethod
|
|
@@ -821,7 +861,7 @@ class Color:
|
|
|
821
861
|
r: int,
|
|
822
862
|
g: int,
|
|
823
863
|
b: int,
|
|
824
|
-
a: float = None,
|
|
864
|
+
a: Optional[float] = None,
|
|
825
865
|
preserve_original: bool = False,
|
|
826
866
|
) -> int:
|
|
827
867
|
"""Convert RGBA channels to a HEXA integer (alpha is optional).\n
|
|
@@ -879,7 +919,7 @@ class Color:
|
|
|
879
919
|
raise ValueError(f"Invalid HEX integer '0x{hex_str}': expected in range [0x000000, 0xFFFFFF]")
|
|
880
920
|
|
|
881
921
|
@staticmethod
|
|
882
|
-
def luminance(r: int, g: int, b: int, output_type: type = None, method: str = "wcag2") -> int | float:
|
|
922
|
+
def luminance(r: int, g: int, b: int, output_type: Optional[type] = None, method: str = "wcag2") -> int | float:
|
|
883
923
|
"""Calculates the relative luminance of a color according to various standards.\n
|
|
884
924
|
----------------------------------------------------------------------------------
|
|
885
925
|
The `output_type` controls the range of the returned luminance value:
|
|
@@ -891,21 +931,21 @@ class Color:
|
|
|
891
931
|
- `"wcag3"` Draft WCAG 3.0 standard with improved coefficients
|
|
892
932
|
- `"simple"` Simple arithmetic mean (less accurate)
|
|
893
933
|
- `"bt601"` ITU-R BT.601 standard (older TV standard)"""
|
|
894
|
-
|
|
934
|
+
_r, _g, _b = r / 255.0, g / 255.0, b / 255.0
|
|
895
935
|
if method == "simple":
|
|
896
|
-
luminance = (
|
|
936
|
+
luminance = (_r + _g + _b) / 3
|
|
897
937
|
elif method == "bt601":
|
|
898
|
-
luminance = 0.299 *
|
|
938
|
+
luminance = 0.299 * _r + 0.587 * _g + 0.114 * _b
|
|
899
939
|
elif method == "wcag3":
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
luminance = 0.2126729 *
|
|
940
|
+
_r = Color._linearize_srgb(_r)
|
|
941
|
+
_g = Color._linearize_srgb(_g)
|
|
942
|
+
_b = Color._linearize_srgb(_b)
|
|
943
|
+
luminance = 0.2126729 * _r + 0.7151522 * _g + 0.0721750 * _b
|
|
904
944
|
else:
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
luminance = 0.2126 *
|
|
945
|
+
_r = Color._linearize_srgb(_r)
|
|
946
|
+
_g = Color._linearize_srgb(_g)
|
|
947
|
+
_b = Color._linearize_srgb(_b)
|
|
948
|
+
luminance = 0.2126 * _r + 0.7152 * _g + 0.0722 * _b
|
|
909
949
|
if output_type == int:
|
|
910
950
|
return round(luminance * 100)
|
|
911
951
|
elif output_type == float:
|
|
@@ -922,47 +962,38 @@ class Color:
|
|
|
922
962
|
return ((c + 0.055) / 1.055)**2.4
|
|
923
963
|
|
|
924
964
|
@staticmethod
|
|
925
|
-
def text_color_for_on_bg(text_bg_color:
|
|
926
|
-
was_hexa, was_int = Color.is_valid_hexa(text_bg_color), isinstance(text_bg_color, int)
|
|
965
|
+
def text_color_for_on_bg(text_bg_color: Rgba | Hexa) -> rgba | hexa | int:
|
|
966
|
+
was_hexa, was_int = Color.is_valid_hexa(text_bg_color), isinstance(text_bg_color, int) # type: ignore[assignment]
|
|
927
967
|
text_bg_color = Color.to_rgba(text_bg_color)
|
|
928
968
|
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))
|
|
969
|
+
return (((0xFFFFFF if was_int else hexa("", 255, 255, 255)) if was_hexa else rgba(255, 255, 255, _validate=False))
|
|
970
|
+
if brightness < 128 else
|
|
930
971
|
((0x000 if was_int else hexa("", 0, 0, 0)) if was_hexa else rgba(0, 0, 0, _validate=False)))
|
|
931
972
|
|
|
932
973
|
@staticmethod
|
|
933
|
-
def adjust_lightness(color:
|
|
974
|
+
def adjust_lightness(color: Rgba | Hexa, lightness_change: float) -> rgba | hexa:
|
|
934
975
|
"""In- or decrease the lightness of the input color.\n
|
|
935
976
|
-----------------------------------------------------------------------------------------------------
|
|
936
977
|
- color (rgba|hexa): HEX or RGBA color
|
|
937
978
|
- lightness_change (float): float between -1.0 (darken by `100%`) and 1.0 (lighten by `100%`)\n
|
|
938
979
|
-----------------------------------------------------------------------------------------------------
|
|
939
980
|
returns (rgba|hexa): the adjusted color in the format of the input color"""
|
|
940
|
-
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
|
-
)
|
|
981
|
+
was_hexa = Color.is_valid_hexa(color) # type: ignore[assignment]
|
|
982
|
+
_color: hsla = Color.to_hsla(color) # type: ignore[assignment]
|
|
983
|
+
h, s, l, a = (int(_color[0]), int(_color[1]), int(_color[2]), _color[3] if Color.has_alpha(_color) else None)
|
|
948
984
|
l = int(max(0, min(100, l + lightness_change * 100)))
|
|
949
985
|
return hsla(h, s, l, a, _validate=False).to_hexa() if was_hexa else hsla(h, s, l, a, _validate=False).to_rgba()
|
|
950
986
|
|
|
951
987
|
@staticmethod
|
|
952
|
-
def adjust_saturation(color:
|
|
988
|
+
def adjust_saturation(color: Rgba | Hsla | Hexa, saturation_change: float) -> rgba | hexa:
|
|
953
989
|
"""In- or decrease the saturation of the input color.\n
|
|
954
990
|
-----------------------------------------------------------------------------------------------------------
|
|
955
991
|
- color (rgba|hexa): HEX or RGBA color
|
|
956
992
|
- saturation_change (float): float between -1.0 (saturate by `100%`) and 1.0 (desaturate by `100%`)\n
|
|
957
993
|
-----------------------------------------------------------------------------------------------------------
|
|
958
994
|
returns (rgba|hexa): the adjusted color in the format of the input color"""
|
|
959
|
-
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
|
-
)
|
|
995
|
+
was_hexa = Color.is_valid_hexa(color) # type: ignore[assignment]
|
|
996
|
+
_color: hsla = Color.to_hsla(color) # type: ignore[assignment]
|
|
997
|
+
h, s, l, a = (int(_color[0]), int(_color[1]), int(_color[2]), _color[3] if Color.has_alpha(_color) else None)
|
|
967
998
|
s = int(max(0, min(100, s + saturation_change * 100)))
|
|
968
999
|
return hsla(h, s, l, a, _validate=False).to_hexa() if was_hexa else hsla(h, s, l, a, _validate=False).to_rgba()
|