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