chromatic-python 0.2.0__tar.gz → 0.2.1__tar.gz
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.
- {chromatic_python-0.2.0/chromatic_python.egg-info → chromatic_python-0.2.1}/PKG-INFO +3 -3
- chromatic_python-0.2.1/chromatic/_version.py +21 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic/color/core.py +238 -193
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic/color/core.pyi +50 -46
- chromatic_python-0.2.1/chromatic/color/iterators.py +166 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic/color/palette.py +34 -161
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic/color/palette.pyi +15 -39
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic/demo.py +2 -2
- {chromatic_python-0.2.0 → chromatic_python-0.2.1/chromatic_python.egg-info}/PKG-INFO +3 -3
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic_python.egg-info/SOURCES.txt +1 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/pyproject.toml +10 -8
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/tests/test_color_str.py +49 -26
- chromatic_python-0.2.0/chromatic/_version.py +0 -16
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/.gitattributes +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/.gitignore +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/LICENSE +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/README.md +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic/__init__.py +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic/_typing.py +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic/ascii/__init__.py +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic/ascii/_array.py +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic/ascii/_curses.py +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic/ascii/_glyph_proc.py +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic/color/__init__.py +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic/color/colorconv.py +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic/data/__init__.py +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic/data/__init__.pyi +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic/data/fonts/IBM_VGA_437_8x16.ttf +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic/data/fonts/consolas.ttf +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic/data/images/butterfly.jpg +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic/data/images/escher.png +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic/data/images/goblin_virus.png +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic/data/images/hotdog.jpg +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic_python.egg-info/dependency_links.txt +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic_python.egg-info/requires.txt +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/chromatic_python.egg-info/top_level.txt +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/logo/logo.ANS +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/logo/logo.PNG +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/setup.cfg +0 -0
- {chromatic_python-0.2.0 → chromatic_python-0.2.1}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: chromatic-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: ANSI art image processing and colored terminal text
|
|
5
5
|
Author: crypt0lith
|
|
6
6
|
License: MIT License
|
|
@@ -27,7 +27,6 @@ License: MIT License
|
|
|
27
27
|
Project-URL: Homepage, https://github.com/crypt0lith/chromatic
|
|
28
28
|
Keywords: ansi,ascii,art,font,image,terminal,parser
|
|
29
29
|
Classifier: Programming Language :: Python :: 3.12
|
|
30
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
31
30
|
Classifier: Operating System :: OS Independent
|
|
32
31
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
33
32
|
Classifier: Typing :: Typed
|
|
@@ -42,6 +41,7 @@ Requires-Dist: pillow~=10.4.0
|
|
|
42
41
|
Requires-Dist: scikit-image~=0.25.0rc1
|
|
43
42
|
Requires-Dist: scikit-learn~=1.5.2
|
|
44
43
|
Requires-Dist: scipy~=1.14.1
|
|
44
|
+
Dynamic: license-file
|
|
45
45
|
|
|
46
46
|

|
|
47
47
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
6
|
+
TYPE_CHECKING = False
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
11
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
12
|
+
else:
|
|
13
|
+
VERSION_TUPLE = object
|
|
14
|
+
|
|
15
|
+
version: str
|
|
16
|
+
__version__: str
|
|
17
|
+
__version_tuple__: VERSION_TUPLE
|
|
18
|
+
version_tuple: VERSION_TUPLE
|
|
19
|
+
|
|
20
|
+
__version__ = version = '0.2.1'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 2, 1)
|
|
@@ -2,24 +2,23 @@ __all__ = [
|
|
|
2
2
|
'CSI',
|
|
3
3
|
'Color',
|
|
4
4
|
'ColorStr',
|
|
5
|
+
'SGR_RESET',
|
|
5
6
|
'SgrParameter',
|
|
6
7
|
'SgrSequence',
|
|
7
8
|
'ansicolor24Bit',
|
|
8
9
|
'ansicolor4Bit',
|
|
9
10
|
'ansicolor8Bit',
|
|
11
|
+
'color_chain',
|
|
10
12
|
'colorbytes',
|
|
11
13
|
'get_ansi_type',
|
|
12
|
-
'hsl_gradient',
|
|
13
14
|
'randcolor',
|
|
14
|
-
'
|
|
15
|
-
'rgb_luma_transform',
|
|
16
|
-
'SGR_RESET',
|
|
15
|
+
'rgb2ansi_escape',
|
|
17
16
|
]
|
|
18
17
|
|
|
19
|
-
import math
|
|
20
18
|
import operator as op
|
|
21
19
|
import os
|
|
22
20
|
import random
|
|
21
|
+
import sys
|
|
23
22
|
from collections import Counter
|
|
24
23
|
from collections.abc import Buffer
|
|
25
24
|
from copy import deepcopy
|
|
@@ -53,7 +52,6 @@ from .colorconv import *
|
|
|
53
52
|
from .._typing import (
|
|
54
53
|
AnsiColorAlias,
|
|
55
54
|
ColorDictKeys,
|
|
56
|
-
Float3Tuple,
|
|
57
55
|
Int3Tuple,
|
|
58
56
|
RGBVectorLike,
|
|
59
57
|
is_matching_typed_dict,
|
|
@@ -218,7 +216,7 @@ class colorbytes(bytes):
|
|
|
218
216
|
|
|
219
217
|
fmt: AnsiColorType = cls if cls is not colorbytes else DEFAULT_ANSI
|
|
220
218
|
try:
|
|
221
|
-
inst = bytes.__new__(fmt,
|
|
219
|
+
inst = bytes.__new__(fmt, rgb2ansi_escape(fmt, *rgb.copy().popitem()))
|
|
222
220
|
except TypeError:
|
|
223
221
|
print(vars())
|
|
224
222
|
raise
|
|
@@ -232,7 +230,7 @@ class colorbytes(bytes):
|
|
|
232
230
|
) from None
|
|
233
231
|
if (is_subtype := cls is not colorbytes) and type(__ansi) is cls:
|
|
234
232
|
return cast(AnsiColorFormat, __ansi)
|
|
235
|
-
match __ansi
|
|
233
|
+
match _unwrap_ansi_escape(__ansi):
|
|
236
234
|
case [color]:
|
|
237
235
|
typ = ansicolor4Bit
|
|
238
236
|
k, rgb = _ANSI16C_I2KV[int(color)]
|
|
@@ -247,7 +245,7 @@ class colorbytes(bytes):
|
|
|
247
245
|
case _:
|
|
248
246
|
raise ValueError
|
|
249
247
|
if typ is not cls:
|
|
250
|
-
__ansi =
|
|
248
|
+
__ansi = rgb2ansi_escape(
|
|
251
249
|
cls if is_subtype else typ, mode=cast(ColorDictKeys, k), rgb=rgb
|
|
252
250
|
)
|
|
253
251
|
inst = bytes.__new__(typ, __ansi)
|
|
@@ -352,7 +350,7 @@ class ansicolor24Bit(colorbytes):
|
|
|
352
350
|
|
|
353
351
|
|
|
354
352
|
_SUPPORTS_256 = frozenset(
|
|
355
|
-
|
|
353
|
+
[
|
|
356
354
|
'ANSICON',
|
|
357
355
|
'COLORTERM',
|
|
358
356
|
'ConEmuANSI',
|
|
@@ -361,7 +359,7 @@ _SUPPORTS_256 = frozenset(
|
|
|
361
359
|
'TERMINAL_EMULATOR',
|
|
362
360
|
'TERM_PROGRAM',
|
|
363
361
|
'WT_SESSION',
|
|
364
|
-
|
|
362
|
+
]
|
|
365
363
|
)
|
|
366
364
|
|
|
367
365
|
|
|
@@ -385,11 +383,8 @@ def is_vt_proc_enabled():
|
|
|
385
383
|
return True
|
|
386
384
|
|
|
387
385
|
|
|
388
|
-
|
|
389
|
-
return ansicolor8Bit if is_vt_proc_enabled() else ansicolor4Bit
|
|
390
|
-
|
|
386
|
+
DEFAULT_ANSI = ansicolor8Bit if is_vt_proc_enabled() else ansicolor4Bit
|
|
391
387
|
|
|
392
|
-
DEFAULT_ANSI = get_term_ansi_default()
|
|
393
388
|
_ANSI_COLOR_TYPES = frozenset(colorbytes.__subclasses__())
|
|
394
389
|
|
|
395
390
|
|
|
@@ -401,6 +396,75 @@ def _is_ansi_type(typ: type):
|
|
|
401
396
|
return False
|
|
402
397
|
|
|
403
398
|
|
|
399
|
+
@lru_cache
|
|
400
|
+
def _sgr_re_pattern():
|
|
401
|
+
import re
|
|
402
|
+
|
|
403
|
+
def group(*choices: *tuple[str, ...]):
|
|
404
|
+
return '(?:' + '|'.join(choices) + ')'
|
|
405
|
+
|
|
406
|
+
def params(*choices: *tuple[str, ...]):
|
|
407
|
+
return ';'.join(choices)
|
|
408
|
+
|
|
409
|
+
u8_no_leading_zeros = group(r'25[0-5]', r'2[0-4]\d', r'1\d\d', r'[1-9]\d', r'\d')
|
|
410
|
+
pat = group(
|
|
411
|
+
group(
|
|
412
|
+
params(
|
|
413
|
+
'[3-4]8',
|
|
414
|
+
group(
|
|
415
|
+
params('2', *(u8_no_leading_zeros for _ in range(3))),
|
|
416
|
+
params('5', u8_no_leading_zeros),
|
|
417
|
+
),
|
|
418
|
+
)
|
|
419
|
+
),
|
|
420
|
+
group('10', '9', '4', '3') + '[1-7]',
|
|
421
|
+
group('10', '9', '[1-6]') + '0',
|
|
422
|
+
'6[1-3]',
|
|
423
|
+
'5[2-5]',
|
|
424
|
+
'[2-4]9',
|
|
425
|
+
'28',
|
|
426
|
+
'2[0-6]',
|
|
427
|
+
r'1\d',
|
|
428
|
+
r'\d',
|
|
429
|
+
)
|
|
430
|
+
return re.compile(rf'\x1b\[{pat[:-1]}(?:;{pat})*)?m')
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def _split_ansi_escape(__s: str) -> Optional[list[tuple['SgrSequence', str]]]:
|
|
434
|
+
out = []
|
|
435
|
+
i = 0
|
|
436
|
+
for m in _sgr_re_pattern().finditer(__s):
|
|
437
|
+
text = __s[i : (j := m.start())]
|
|
438
|
+
if i != j:
|
|
439
|
+
out.append(text)
|
|
440
|
+
ansi = _unwrap_ansi_escape(__s[j : (i := m.end())].encode())
|
|
441
|
+
if any(ansi):
|
|
442
|
+
out.append(SgrSequence(map(int, ansi)))
|
|
443
|
+
if i + 1 < len(__s):
|
|
444
|
+
out.append(__s[i:])
|
|
445
|
+
if not any(isinstance(x, SgrSequence) for x in out):
|
|
446
|
+
return
|
|
447
|
+
n = len(out)
|
|
448
|
+
tmp = []
|
|
449
|
+
for idx, x in enumerate(out):
|
|
450
|
+
if idx + 1 < n and type(x) is type(out[idx + 1]):
|
|
451
|
+
out[idx + 1] = x + out[idx + 1]
|
|
452
|
+
else:
|
|
453
|
+
tmp.append(x)
|
|
454
|
+
out = tmp
|
|
455
|
+
if out and len(out) % 2 != 0:
|
|
456
|
+
out.append({SgrSequence: str, str: SgrSequence}[type(out[-1])]())
|
|
457
|
+
return [(a, b) if isinstance(a, SgrSequence) else (b, a) for a, b in zip(out[::2], out[1::2])]
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _unwrap_ansi_escape(__b: bytes):
|
|
461
|
+
return __b.removeprefix(CSI).removesuffix(b'm').split(b';')
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _concat_ansi_escape(__it: Iterable[bytes]):
|
|
465
|
+
return b'\x1b[%sm' % b';'.join(__it)
|
|
466
|
+
|
|
467
|
+
|
|
404
468
|
AnsiColorFormat: TypeAlias = ansicolor4Bit | ansicolor8Bit | ansicolor24Bit
|
|
405
469
|
AnsiColorType: TypeAlias = type[AnsiColorFormat]
|
|
406
470
|
AnsiColorParam: TypeAlias = AnsiColorAlias | AnsiColorType
|
|
@@ -437,7 +501,7 @@ def get_ansi_type(typ):
|
|
|
437
501
|
) from None
|
|
438
502
|
|
|
439
503
|
|
|
440
|
-
def
|
|
504
|
+
def rgb2ansi_escape(ret_format, mode, rgb):
|
|
441
505
|
ret_format = get_ansi_type(ret_format)
|
|
442
506
|
assert len(rgb) == 3, 'length of RGB value is not 3'
|
|
443
507
|
try:
|
|
@@ -522,8 +586,7 @@ class SgrParamWrapper:
|
|
|
522
586
|
return hash(self._value_)
|
|
523
587
|
|
|
524
588
|
def __eq__(self, other):
|
|
525
|
-
|
|
526
|
-
if cls is other_cls or issubclass(other_cls, bytes):
|
|
589
|
+
if type(self) is type(other) or isinstance(other, bytes):
|
|
527
590
|
return hash(self) == hash(other)
|
|
528
591
|
return NotImplemented
|
|
529
592
|
|
|
@@ -534,10 +597,8 @@ class SgrParamWrapper:
|
|
|
534
597
|
return f"{type(self).__name__}({self._value_})"
|
|
535
598
|
|
|
536
599
|
def is_same_kind(self, other):
|
|
537
|
-
if self == other:
|
|
538
|
-
return True
|
|
539
600
|
try:
|
|
540
|
-
return next(_iter_sgr(other))
|
|
601
|
+
return self == other or self._value_ == next(_iter_sgr(other))
|
|
541
602
|
except (TypeError, StopIteration, RuntimeError):
|
|
542
603
|
return False
|
|
543
604
|
|
|
@@ -548,9 +609,6 @@ class SgrParamWrapper:
|
|
|
548
609
|
return isinstance(self._value_, colorbytes)
|
|
549
610
|
|
|
550
611
|
|
|
551
|
-
# SgrParamWrapper.__name__ = SgrParameter.__name__.lower()
|
|
552
|
-
|
|
553
|
-
|
|
554
612
|
@lru_cache
|
|
555
613
|
def _get_sgr_bitmask[_T: (bytes, bytearray, Buffer)](__x: _T) -> list[int]:
|
|
556
614
|
"""Return a list of integers from a bytestring of ANSI SGR parameters.
|
|
@@ -575,7 +633,9 @@ def _get_sgr_bitmask[_T: (bytes, bytearray, Buffer)](__x: _T) -> list[int]:
|
|
|
575
633
|
return allocated
|
|
576
634
|
|
|
577
635
|
|
|
578
|
-
def _iter_normalized_sgr
|
|
636
|
+
def _iter_normalized_sgr[
|
|
637
|
+
_T: (Buffer, SgrParamWrapper, int)
|
|
638
|
+
](__iter: Buffer | Iterable[_T]) -> Iterator[AnsiColorFormat | int]:
|
|
579
639
|
if isinstance(__iter, Buffer):
|
|
580
640
|
yield from _get_sgr_bitmask(__iter)
|
|
581
641
|
else:
|
|
@@ -635,13 +695,13 @@ def _gen_colorbytes(__iter: Iterable[int]) -> Iterator[bytes | AnsiColorFormat]:
|
|
|
635
695
|
break
|
|
636
696
|
|
|
637
697
|
|
|
638
|
-
def _iter_sgr(__x):
|
|
698
|
+
def _iter_sgr[_T: (Buffer, int)](__x: _T | Iterable[_T]):
|
|
639
699
|
if isinstance(__x, int):
|
|
640
700
|
__x = [__x]
|
|
641
701
|
return _gen_colorbytes(_iter_normalized_sgr(__x))
|
|
642
702
|
|
|
643
703
|
|
|
644
|
-
class SgrSequence:
|
|
704
|
+
class SgrSequence(Sequence[SgrParamWrapper]):
|
|
645
705
|
|
|
646
706
|
def append(self, __value):
|
|
647
707
|
if __value not in _SGR_PARAM_VALUES:
|
|
@@ -677,9 +737,11 @@ class SgrSequence:
|
|
|
677
737
|
if self.is_color():
|
|
678
738
|
return next((v for v in self if v.is_color() and __key in v._value_.rgb_dict), None)
|
|
679
739
|
|
|
680
|
-
def index(self, value):
|
|
740
|
+
def index(self, value, start: SupportsIndex = 0, stop: SupportsIndex = sys.maxsize):
|
|
681
741
|
try:
|
|
682
|
-
return
|
|
742
|
+
return op.index(start) + next(
|
|
743
|
+
i for i, p in enumerate(self[start:stop]) if p.is_same_kind(value)
|
|
744
|
+
)
|
|
683
745
|
except StopIteration:
|
|
684
746
|
raise ValueError(f"{value!r} not in sequence") from None
|
|
685
747
|
|
|
@@ -722,7 +784,7 @@ class SgrSequence:
|
|
|
722
784
|
|
|
723
785
|
def __add__(self, other):
|
|
724
786
|
if type(self) is type(other):
|
|
725
|
-
return SgrSequence(
|
|
787
|
+
return SgrSequence(x for xs in (self, other) for x in xs)
|
|
726
788
|
if isinstance(other, str):
|
|
727
789
|
return str(self) + other
|
|
728
790
|
|
|
@@ -738,7 +800,7 @@ class SgrSequence:
|
|
|
738
800
|
def __bytes__(self):
|
|
739
801
|
if self._bytes_ is None:
|
|
740
802
|
if self._sgr_params_:
|
|
741
|
-
self._bytes_ =
|
|
803
|
+
self._bytes_ = _concat_ansi_escape(self.values())
|
|
742
804
|
else:
|
|
743
805
|
self._bytes_ = bytes()
|
|
744
806
|
return self._bytes_
|
|
@@ -795,10 +857,10 @@ class SgrSequence:
|
|
|
795
857
|
return SgrSequence(self._sgr_params_ + other._sgr_params_)
|
|
796
858
|
|
|
797
859
|
def __init__(self, __iter=None, *, ansi_type=None) -> None:
|
|
798
|
-
|
|
799
|
-
|
|
860
|
+
if type(self) is type(__iter):
|
|
861
|
+
__iter: SgrSequence
|
|
800
862
|
other = __iter.__copy__()
|
|
801
|
-
for attr in
|
|
863
|
+
for attr in type(self).__slots__:
|
|
802
864
|
setattr(self, attr, getattr(other, attr))
|
|
803
865
|
return
|
|
804
866
|
|
|
@@ -866,14 +928,17 @@ class SgrSequence:
|
|
|
866
928
|
self._has_bright_colors_ = False
|
|
867
929
|
self._sgr_params_ = [self._sgr_params_.pop()]
|
|
868
930
|
self._rgb_dict_ = {}
|
|
869
|
-
self._bytes_ =
|
|
931
|
+
self._bytes_ = _concat_ansi_escape(map(bytes, self._sgr_params_))
|
|
870
932
|
|
|
871
933
|
def __iter__(self):
|
|
872
934
|
return iter(self._sgr_params_)
|
|
873
935
|
|
|
936
|
+
def __len__(self):
|
|
937
|
+
return len(self._sgr_params_)
|
|
938
|
+
|
|
874
939
|
def __radd__(self, other):
|
|
875
940
|
if type(self) is type(other):
|
|
876
|
-
return SgrSequence(
|
|
941
|
+
return SgrSequence(x for xs in (other, self) for x in xs)
|
|
877
942
|
if isinstance(other, str):
|
|
878
943
|
return other + str(self)
|
|
879
944
|
raise TypeError(
|
|
@@ -916,13 +981,13 @@ class SgrSequence:
|
|
|
916
981
|
def rgb_dict(self, __value: tuple[AnsiColorType, dict[ColorDictKeys, Optional[Color]]]) -> None:
|
|
917
982
|
ansi_type, color_dict = __value
|
|
918
983
|
for k, v in color_dict.items():
|
|
984
|
+
if self._rgb_dict_.get(k):
|
|
985
|
+
try:
|
|
986
|
+
self.pop(self.index(self.get_color(k)))
|
|
987
|
+
except ValueError as e:
|
|
988
|
+
e.add_note(repr(self))
|
|
989
|
+
raise e
|
|
919
990
|
if v is not None:
|
|
920
|
-
if self._rgb_dict_.get(k):
|
|
921
|
-
try:
|
|
922
|
-
self.pop(self.index(self.get_color(k)))
|
|
923
|
-
except ValueError as e:
|
|
924
|
-
e.add_note(repr(self))
|
|
925
|
-
raise e
|
|
926
991
|
color_bytes = ansi_type.from_rgb({k: v})
|
|
927
992
|
self._rgb_dict_ |= color_bytes._rgb_dict_
|
|
928
993
|
self._sgr_params_.append(SgrParamWrapper(color_bytes))
|
|
@@ -995,7 +1060,7 @@ def _solve_color_spec[
|
|
|
995
1060
|
e = ValueError('too many arguments' if len(out) >= 2 else 'args contain non-RGB values')
|
|
996
1061
|
context = ('invalid color spec', str(e))
|
|
997
1062
|
raise ValueError(': '.join(filter(None, context))) from None
|
|
998
|
-
return SgrSequence(
|
|
1063
|
+
return SgrSequence(ansi_type.from_rgb({k: v}) for k, v in out.items())
|
|
999
1064
|
|
|
1000
1065
|
|
|
1001
1066
|
def _get_color_str_vars(
|
|
@@ -1534,159 +1599,139 @@ class ColorStr(str):
|
|
|
1534
1599
|
return {k: v.rgb for k, v in self._color_dict_.items()}
|
|
1535
1600
|
|
|
1536
1601
|
|
|
1537
|
-
def
|
|
1538
|
-
|
|
1539
|
-
stop: Int3Tuple | Float3Tuple,
|
|
1540
|
-
step: SupportsIndex,
|
|
1541
|
-
num: SupportsIndex = None,
|
|
1542
|
-
ncycles: int | float = float('inf'),
|
|
1543
|
-
replace_idx: tuple[SupportsIndex | Iterable[SupportsIndex], Iterator[Color]] = None,
|
|
1544
|
-
dtype: type[Color] | Callable[[Int3Tuple], int] = Color,
|
|
1545
|
-
):
|
|
1546
|
-
replace_idx, rgb_iter = _resolve_replacement_indices(replace_idx)
|
|
1547
|
-
while abs(float(step)) < 1:
|
|
1548
|
-
step *= 10
|
|
1549
|
-
color_vec = _init_gradient_color_vec(num, start, step, stop)
|
|
1550
|
-
color_iter = iter(color_vec)
|
|
1551
|
-
type_map: dict[type[Color | int], ...] = {Color: lambda x: x.rgb, int: lambda x: hex2rgb(x)}
|
|
1552
|
-
get_rgb_iter_idx: Callable[[Color | int, SupportsIndex], int] = lambda x, ix: rgb2hsl(
|
|
1553
|
-
type_map[type(x)](x)
|
|
1554
|
-
)[ix]
|
|
1555
|
-
next_rgb_iter = None
|
|
1556
|
-
prev_output = None
|
|
1557
|
-
while ncycles > 0:
|
|
1558
|
-
try:
|
|
1559
|
-
cur_iter = next(color_iter)
|
|
1560
|
-
if cur_iter != prev_output:
|
|
1561
|
-
for idx in replace_idx:
|
|
1562
|
-
try:
|
|
1563
|
-
next_rgb_iter = next(rgb_iter)
|
|
1564
|
-
cur_iter = list(cur_iter)
|
|
1565
|
-
cur_iter[idx] = get_rgb_iter_idx(next_rgb_iter, idx)
|
|
1566
|
-
except StopIteration:
|
|
1567
|
-
raise GeneratorExit
|
|
1568
|
-
except KeyError:
|
|
1569
|
-
raise TypeError(
|
|
1570
|
-
f"Expected iterator to return "
|
|
1571
|
-
f"{repr(Color.__qualname__)} or {repr(int.__qualname__)}, "
|
|
1572
|
-
f"got {repr(type(next_rgb_iter).__qualname__)} instead"
|
|
1573
|
-
) from None
|
|
1574
|
-
output = hsl2rgb(cast(Float3Tuple, cur_iter))
|
|
1575
|
-
if callable(dtype):
|
|
1576
|
-
output = dtype(output)
|
|
1577
|
-
yield output
|
|
1578
|
-
prev_output = cur_iter
|
|
1579
|
-
except StopIteration:
|
|
1580
|
-
ncycles -= 1
|
|
1581
|
-
color_vec.reverse()
|
|
1582
|
-
color_iter = iter(color_vec)
|
|
1583
|
-
except GeneratorExit:
|
|
1584
|
-
break
|
|
1602
|
+
def _color_str_to_mask(cs: ColorStr) -> tuple[SgrSequence, str]:
|
|
1603
|
+
return cs._sgr_, cs.base_str
|
|
1585
1604
|
|
|
1586
1605
|
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
):
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1606
|
+
class color_chain:
|
|
1607
|
+
|
|
1608
|
+
def extend(self, other):
|
|
1609
|
+
if isinstance(other, color_chain):
|
|
1610
|
+
self._masks_.extend(other._masks_[:])
|
|
1611
|
+
elif isinstance(other, ColorStr):
|
|
1612
|
+
self._masks_.append(_color_str_to_mask(other))
|
|
1613
|
+
elif isinstance(other, str):
|
|
1614
|
+
self._masks_.append((SgrSequence(), other))
|
|
1615
|
+
else: #
|
|
1616
|
+
raise TypeError
|
|
1617
|
+
|
|
1618
|
+
@classmethod
|
|
1619
|
+
def from_masks(cls, masks, ansi_type=None):
|
|
1620
|
+
if isinstance(masks, Sequence) and all(
|
|
1621
|
+
isinstance(x, tuple)
|
|
1622
|
+
and len(x) == 2
|
|
1623
|
+
and isinstance(x[0], SgrSequence)
|
|
1624
|
+
and isinstance(x[1], str)
|
|
1625
|
+
for x in masks
|
|
1626
|
+
):
|
|
1627
|
+
return cls._from_masks_unchecked(masks, get_ansi_type(ansi_type or DEFAULT_ANSI))
|
|
1628
|
+
raise TypeError
|
|
1629
|
+
|
|
1630
|
+
@classmethod
|
|
1631
|
+
def _from_masks_unchecked(cls, masks, ansi_type):
|
|
1632
|
+
inst = object.__new__(cls)
|
|
1633
|
+
prev_fg = prev_bg = None
|
|
1634
|
+
inst._masks_ = []
|
|
1635
|
+
for sgr, s in masks:
|
|
1636
|
+
if prev_fg is not None and prev_fg == sgr.fg:
|
|
1637
|
+
sgr.rgb_dict = (ansi_type, {'fg': None})
|
|
1638
|
+
if prev_bg is not None and prev_bg == sgr.bg:
|
|
1639
|
+
sgr.rgb_dict = (ansi_type, {'bg': None})
|
|
1640
|
+
inst._masks_.append((sgr, s))
|
|
1641
|
+
prev_fg, prev_bg = sgr.fg, sgr.bg
|
|
1642
|
+
inst._ansi_type_ = ansi_type
|
|
1643
|
+
return inst
|
|
1644
|
+
|
|
1645
|
+
def __add__(self, other):
|
|
1646
|
+
if isinstance(other, (color_chain, ColorStr)):
|
|
1647
|
+
other_masks: tuple[tuple[SgrSequence, str], ...] = (
|
|
1648
|
+
other.masks if isinstance(other, color_chain) else (_color_str_to_mask(other),)
|
|
1596
1649
|
)
|
|
1597
|
-
|
|
1598
|
-
|
|
1650
|
+
if self._masks_ and other_masks:
|
|
1651
|
+
match [
|
|
1652
|
+
(self._masks_[-1][0].fg, self._masks_[-1][0].bg),
|
|
1653
|
+
(other_masks[0][0].fg, other_masks[0][0].bg),
|
|
1654
|
+
]:
|
|
1655
|
+
case [(None, tuple() as bg), (tuple() as fg, None)] | (
|
|
1656
|
+
[(tuple() as fg, None), (None, tuple() as bg)]
|
|
1657
|
+
):
|
|
1658
|
+
return self._from_masks_unchecked(
|
|
1659
|
+
[
|
|
1660
|
+
*self._masks_[:-1],
|
|
1661
|
+
(
|
|
1662
|
+
color_chain(fg=fg, bg=bg)._masks_.pop()[0],
|
|
1663
|
+
self._masks_[-1][1] + other_masks[0][1],
|
|
1664
|
+
),
|
|
1665
|
+
*other_masks[1:],
|
|
1666
|
+
],
|
|
1667
|
+
ansi_type=self._ansi_type_,
|
|
1668
|
+
)
|
|
1669
|
+
case _:
|
|
1670
|
+
return self._from_masks_unchecked(
|
|
1671
|
+
self.masks + other_masks, ansi_type=self._ansi_type_
|
|
1672
|
+
)
|
|
1673
|
+
elif isinstance(other, str):
|
|
1674
|
+
if len(self._masks_) > 0:
|
|
1675
|
+
return self._from_masks_unchecked(
|
|
1676
|
+
[*self.masks[:-1], (self._masks_[-1][0], self._masks_[-1][1] + other)],
|
|
1677
|
+
ansi_type=self._ansi_type_,
|
|
1678
|
+
)
|
|
1679
|
+
return self._from_masks_unchecked(
|
|
1680
|
+
[*self.masks, (SgrSequence(), other)], ansi_type=self._ansi_type_
|
|
1681
|
+
)
|
|
1682
|
+
return NotImplemented
|
|
1683
|
+
|
|
1684
|
+
def __call__(self, __obj=None):
|
|
1685
|
+
return "%s%s" % (self, __obj)
|
|
1686
|
+
|
|
1687
|
+
def __iadd__(self, other):
|
|
1688
|
+
self.extend(other)
|
|
1689
|
+
return self
|
|
1690
|
+
|
|
1691
|
+
def __init__(self, **kwargs):
|
|
1692
|
+
self._ansi_type_ = get_ansi_type(kwargs.get('ansi_type', DEFAULT_ANSI))
|
|
1693
|
+
if kwargs.get('sgr_params') is not None:
|
|
1694
|
+
sgr = SgrSequence(kwargs.get('sgr_params'))
|
|
1599
1695
|
else:
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
return replace_idx, rgb_iter
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
def _init_gradient_color_vec(
|
|
1613
|
-
num: SupportsIndex,
|
|
1614
|
-
start: Int3Tuple | Float3Tuple,
|
|
1615
|
-
step: SupportsIndex,
|
|
1616
|
-
stop: Int3Tuple | Float3Tuple,
|
|
1617
|
-
):
|
|
1618
|
-
def convert_bounds(rgb: Int3Tuple):
|
|
1619
|
-
if all(0 <= n <= 255 for n in rgb):
|
|
1620
|
-
return rgb2hsl(rgb)
|
|
1621
|
-
raise ValueError
|
|
1622
|
-
|
|
1623
|
-
start, stop = tuple(map(convert_bounds, (start, stop)))
|
|
1624
|
-
start_h, start_s, start_l = start
|
|
1625
|
-
stop_h, stop_s, stop_l = stop
|
|
1626
|
-
if num:
|
|
1627
|
-
num_samples = num
|
|
1628
|
-
else:
|
|
1629
|
-
abs_h = abs(stop_h - start_h)
|
|
1630
|
-
h_diff = min(abs_h, 360 - abs_h)
|
|
1631
|
-
dist = math.sqrt(h_diff**2 + (stop_s - start_s) ** 2 + (stop_l - start_l) ** 2)
|
|
1632
|
-
num_samples = max(int(dist / float(step)), 1)
|
|
1633
|
-
color_vec = [np.linspace(*bounds, num=num_samples, dtype=float) for bounds in zip(start, stop)]
|
|
1634
|
-
color_vec = list(zip(*color_vec))
|
|
1635
|
-
return color_vec
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
def rgb_luma_transform(
|
|
1639
|
-
rgb: Int3Tuple,
|
|
1640
|
-
start: SupportsIndex = None,
|
|
1641
|
-
num: SupportsIndex = 50,
|
|
1642
|
-
step: SupportsIndex = 1,
|
|
1643
|
-
cycle: bool | Literal['wave'] = False,
|
|
1644
|
-
ncycles: int | float = float('inf'),
|
|
1645
|
-
gradient: Int3Tuple = None,
|
|
1646
|
-
dtype: type[Color] = None,
|
|
1647
|
-
) -> Iterator[Int3Tuple | int | Color]:
|
|
1648
|
-
if dtype is None:
|
|
1649
|
-
ret_type = tuple
|
|
1650
|
-
elif issubclass(dtype, int):
|
|
1651
|
-
ret_type = lambda x: dtype(rgb2hex(x))
|
|
1652
|
-
is_cycle = bool(cycle is not False)
|
|
1653
|
-
is_oscillator = cycle == 'wave'
|
|
1654
|
-
if is_oscillator:
|
|
1655
|
-
ncycles *= 2
|
|
1656
|
-
h, s, luma = rgb2hsl(rgb)
|
|
1657
|
-
luma_linspace = [*np.linspace(start=0, stop=1, num=num)][::step]
|
|
1658
|
-
if start:
|
|
1659
|
-
start = min(max(float(start), 0), 1)
|
|
1660
|
-
luma = min(luma_linspace, key=lambda x: abs(x - start))
|
|
1661
|
-
start_idx = luma_linspace.index(luma)
|
|
1662
|
-
remaining_indices = luma_linspace[start_idx:]
|
|
1663
|
-
luma_iter = iter(remaining_indices)
|
|
1664
|
-
else:
|
|
1665
|
-
luma_iter = iter(luma_linspace)
|
|
1696
|
+
sgr = SgrSequence()
|
|
1697
|
+
v: Int3Tuple | Color | int
|
|
1698
|
+
for k in kwargs.keys() & {'fg', 'bg'}:
|
|
1699
|
+
if (v := kwargs[k]) is None:
|
|
1700
|
+
continue
|
|
1701
|
+
elif isinstance(v, int):
|
|
1702
|
+
v = hex2rgb(v)
|
|
1703
|
+
sgr += SgrSequence(self._ansi_type_.from_rgb({k: v}))
|
|
1704
|
+
self._masks_ = [(sgr, '')]
|
|
1666
1705
|
|
|
1667
|
-
def
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
_gradient = hsl_gradient(
|
|
1689
|
-
start=rgb, stop=gradient, step=step, num=num, replace_idx=(2, _generator())
|
|
1706
|
+
def __radd__(self, other):
|
|
1707
|
+
if isinstance(other, ColorStr):
|
|
1708
|
+
return color_chain._from_masks_unchecked(
|
|
1709
|
+
(_color_str_to_mask(other),) + self.masks, ansi_type=other.ansi_format
|
|
1710
|
+
)
|
|
1711
|
+
elif isinstance(other, str):
|
|
1712
|
+
if (parsed := _split_ansi_escape(other)) is not None:
|
|
1713
|
+
return color_chain._from_masks_unchecked(
|
|
1714
|
+
parsed + self._masks_[:], ansi_type=self._ansi_type_
|
|
1715
|
+
)
|
|
1716
|
+
else:
|
|
1717
|
+
return color_chain._from_masks_unchecked(
|
|
1718
|
+
[(SgrSequence(), other), *self.masks], ansi_type=self._ansi_type_
|
|
1719
|
+
)
|
|
1720
|
+
return NotImplemented
|
|
1721
|
+
|
|
1722
|
+
def __repr__(self):
|
|
1723
|
+
return "{.__name__}([{!s}], ansi_type={.__name__!r})".format(
|
|
1724
|
+
type(self),
|
|
1725
|
+
', '.join('(%s, %r)' % (bytes(sgr), s) for sgr, s in self._masks_),
|
|
1726
|
+
self._ansi_type_,
|
|
1690
1727
|
)
|
|
1691
|
-
|
|
1692
|
-
|
|
1728
|
+
|
|
1729
|
+
def __str__(self):
|
|
1730
|
+
return ''.join(
|
|
1731
|
+
str(ColorStr(base_str, color_spec=sgr, ansi_type=self._ansi_type_, no_reset=True))
|
|
1732
|
+
for sgr, base_str in self.masks
|
|
1733
|
+
)
|
|
1734
|
+
|
|
1735
|
+
@property
|
|
1736
|
+
def masks(self):
|
|
1737
|
+
return tuple(self._masks_)
|