chromatic-python 0.2.2__tar.gz → 0.3.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.2 → chromatic_python-0.3.1}/.gitignore +2 -2
- {chromatic_python-0.2.2/chromatic_python.egg-info → chromatic_python-0.3.1}/PKG-INFO +2 -2
- chromatic_python-0.3.1/chromatic/__init__.py +8 -0
- chromatic_python-0.3.1/chromatic/__init__.pyi +74 -0
- {chromatic_python-0.2.2 → chromatic_python-0.3.1}/chromatic/_typing.py +73 -32
- {chromatic_python-0.2.2 → chromatic_python-0.3.1}/chromatic/_version.py +2 -2
- chromatic_python-0.3.1/chromatic/color/__init__.py +3 -0
- chromatic_python-0.3.1/chromatic/color/__init__.pyi +86 -0
- {chromatic_python-0.2.2 → chromatic_python-0.3.1}/chromatic/color/colorconv.py +85 -48
- chromatic_python-0.3.1/chromatic/color/core.py +1714 -0
- chromatic_python-0.3.1/chromatic/color/core.pyi +503 -0
- {chromatic_python-0.2.2 → chromatic_python-0.3.1}/chromatic/color/iterators.py +13 -7
- chromatic_python-0.3.1/chromatic/color/palette.py +585 -0
- chromatic_python-0.3.1/chromatic/color/palette.pyi +302 -0
- chromatic_python-0.3.1/chromatic/data/__init__.py +33 -0
- chromatic_python-0.3.1/chromatic/data/__init__.pyi +16 -0
- chromatic_python-0.3.1/chromatic/data/_fetchers.py +55 -0
- chromatic_python-0.3.1/chromatic/data/registry.json +5 -0
- chromatic_python-0.3.1/chromatic/data/userfont.py +138 -0
- chromatic_python-0.3.1/chromatic/data/userfont.pyi +30 -0
- chromatic_python-0.3.1/chromatic/data/userfont.schema.json +28 -0
- {chromatic_python-0.2.2 → chromatic_python-0.3.1}/chromatic/demo.py +186 -139
- chromatic_python-0.3.1/chromatic/image/__init__.pyi +70 -0
- {chromatic_python-0.2.2/chromatic/ascii → chromatic_python-0.3.1/chromatic/image}/_array.py +432 -353
- {chromatic_python-0.2.2/chromatic/ascii → chromatic_python-0.3.1/chromatic/image}/_curses.py +8 -4
- {chromatic_python-0.2.2/chromatic/ascii → chromatic_python-0.3.1/chromatic/image}/_glyph_proc.py +11 -13
- {chromatic_python-0.2.2 → chromatic_python-0.3.1/chromatic_python.egg-info}/PKG-INFO +2 -2
- {chromatic_python-0.2.2 → chromatic_python-0.3.1}/chromatic_python.egg-info/SOURCES.txt +16 -9
- {chromatic_python-0.2.2 → chromatic_python-0.3.1}/chromatic_python.egg-info/requires.txt +1 -0
- {chromatic_python-0.2.2 → chromatic_python-0.3.1}/pyproject.toml +0 -2
- {chromatic_python-0.2.2 → chromatic_python-0.3.1}/requirements.txt +2 -1
- {chromatic_python-0.2.2 → chromatic_python-0.3.1}/tests/test_color_str.py +41 -40
- chromatic_python-0.2.2/chromatic/__init__.py +0 -109
- chromatic_python-0.2.2/chromatic/color/__init__.py +0 -6
- chromatic_python-0.2.2/chromatic/color/core.py +0 -1731
- chromatic_python-0.2.2/chromatic/color/core.pyi +0 -359
- chromatic_python-0.2.2/chromatic/color/palette.py +0 -554
- chromatic_python-0.2.2/chromatic/color/palette.pyi +0 -304
- chromatic_python-0.2.2/chromatic/data/__init__.py +0 -188
- chromatic_python-0.2.2/chromatic/data/__init__.pyi +0 -15
- chromatic_python-0.2.2/chromatic/data/images/hotdog.jpg +0 -0
- {chromatic_python-0.2.2 → chromatic_python-0.3.1}/.gitattributes +0 -0
- {chromatic_python-0.2.2 → chromatic_python-0.3.1}/LICENSE +0 -0
- {chromatic_python-0.2.2 → chromatic_python-0.3.1}/README.md +0 -0
- {chromatic_python-0.2.2/chromatic/data/images → chromatic_python-0.3.1/chromatic/data}/butterfly.jpg +0 -0
- {chromatic_python-0.2.2/chromatic/data/images → chromatic_python-0.3.1/chromatic/data}/escher.png +0 -0
- {chromatic_python-0.2.2 → chromatic_python-0.3.1}/chromatic/data/fonts/consolas.ttf +0 -0
- /chromatic_python-0.2.2/chromatic/data/fonts/IBM_VGA_437_8x16.ttf → /chromatic_python-0.3.1/chromatic/data/fonts/vga437.ttf +0 -0
- {chromatic_python-0.2.2/chromatic/data/images → chromatic_python-0.3.1/chromatic/data}/goblin_virus.png +0 -0
- {chromatic_python-0.2.2/chromatic/ascii → chromatic_python-0.3.1/chromatic/image}/__init__.py +0 -0
- {chromatic_python-0.2.2 → chromatic_python-0.3.1}/chromatic_python.egg-info/dependency_links.txt +0 -0
- {chromatic_python-0.2.2 → chromatic_python-0.3.1}/chromatic_python.egg-info/top_level.txt +0 -0
- {chromatic_python-0.2.2 → chromatic_python-0.3.1}/logo/logo.ANS +0 -0
- {chromatic_python-0.2.2 → chromatic_python-0.3.1}/logo/logo.PNG +0 -0
- {chromatic_python-0.2.2 → chromatic_python-0.3.1}/setup.cfg +0 -0
- {chromatic_python-0.2.2 → chromatic_python-0.3.1}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chromatic-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.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: scikit-image~=0.25.2
|
|
|
42
41
|
Requires-Dist: scikit-learn~=1.6.1
|
|
43
42
|
Requires-Dist: fonttools~=4.58.0
|
|
44
43
|
Requires-Dist: scipy~=1.15.3
|
|
44
|
+
Requires-Dist: lazy_loader~=0.4
|
|
45
45
|
Dynamic: license-file
|
|
46
46
|
|
|
47
47
|

|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'AnsiImage',
|
|
3
|
+
'Back',
|
|
4
|
+
'Color',
|
|
5
|
+
'ColorNamespace',
|
|
6
|
+
'ColorStr',
|
|
7
|
+
'Fore',
|
|
8
|
+
'SgrParameter',
|
|
9
|
+
'Style',
|
|
10
|
+
'__version__',
|
|
11
|
+
'ansi2img',
|
|
12
|
+
'ansi_quantize',
|
|
13
|
+
'ansicolor24Bit',
|
|
14
|
+
'ansicolor4Bit',
|
|
15
|
+
'ansicolor8Bit',
|
|
16
|
+
'ansify',
|
|
17
|
+
'image',
|
|
18
|
+
'ascii2img',
|
|
19
|
+
'ascii_printable',
|
|
20
|
+
'color',
|
|
21
|
+
'colorbytes',
|
|
22
|
+
'contrast_stretch',
|
|
23
|
+
'cp437_printable',
|
|
24
|
+
'data',
|
|
25
|
+
'equalize_white_point',
|
|
26
|
+
'get_font_key',
|
|
27
|
+
'get_font_object',
|
|
28
|
+
'get_glyph_masks',
|
|
29
|
+
'img2ansi',
|
|
30
|
+
'img2ascii',
|
|
31
|
+
'named_color',
|
|
32
|
+
'read_ans',
|
|
33
|
+
'register_userfont',
|
|
34
|
+
'render_ans',
|
|
35
|
+
'reshape_ansi',
|
|
36
|
+
'to_sgr_array',
|
|
37
|
+
]
|
|
38
|
+
from . import color, data, image
|
|
39
|
+
from ._version import version as __version__
|
|
40
|
+
from .color import (
|
|
41
|
+
Back,
|
|
42
|
+
Color,
|
|
43
|
+
ColorNamespace,
|
|
44
|
+
ColorStr,
|
|
45
|
+
Fore,
|
|
46
|
+
SgrParameter,
|
|
47
|
+
Style,
|
|
48
|
+
ansicolor24Bit,
|
|
49
|
+
ansicolor4Bit,
|
|
50
|
+
ansicolor8Bit,
|
|
51
|
+
colorbytes,
|
|
52
|
+
named_color,
|
|
53
|
+
)
|
|
54
|
+
from .data import register_userfont
|
|
55
|
+
from .image import (
|
|
56
|
+
AnsiImage,
|
|
57
|
+
ansi2img,
|
|
58
|
+
ansi_quantize,
|
|
59
|
+
ansify,
|
|
60
|
+
ascii2img,
|
|
61
|
+
ascii_printable,
|
|
62
|
+
contrast_stretch,
|
|
63
|
+
cp437_printable,
|
|
64
|
+
equalize_white_point,
|
|
65
|
+
get_font_key,
|
|
66
|
+
get_font_object,
|
|
67
|
+
img2ansi,
|
|
68
|
+
img2ascii,
|
|
69
|
+
read_ans,
|
|
70
|
+
render_ans,
|
|
71
|
+
reshape_ansi,
|
|
72
|
+
to_sgr_array,
|
|
73
|
+
)
|
|
74
|
+
from .image._glyph_proc import get_glyph_masks
|
|
@@ -14,12 +14,13 @@ from typing import (
|
|
|
14
14
|
Concatenate,
|
|
15
15
|
Hashable,
|
|
16
16
|
Iterable,
|
|
17
|
-
Literal,
|
|
17
|
+
Literal as L,
|
|
18
18
|
NamedTuple,
|
|
19
19
|
Optional,
|
|
20
20
|
ParamSpec,
|
|
21
21
|
Protocol,
|
|
22
22
|
Sequence,
|
|
23
|
+
TYPE_CHECKING,
|
|
23
24
|
Type,
|
|
24
25
|
TypeAlias,
|
|
25
26
|
TypeAliasType,
|
|
@@ -40,7 +41,8 @@ from PIL.ImageFont import FreeTypeFont
|
|
|
40
41
|
from numpy import dtype, float64, generic, ndarray, number, uint8
|
|
41
42
|
from numpy._typing import NDArray, _ArrayLike
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
if TYPE_CHECKING:
|
|
45
|
+
from .data import UserFont
|
|
44
46
|
|
|
45
47
|
_P = ParamSpec('_P')
|
|
46
48
|
_T = TypeVar('_T')
|
|
@@ -48,11 +50,15 @@ _T_co = TypeVar('_T_co', covariant=True)
|
|
|
48
50
|
_T_contra = TypeVar('_T_contra', contravariant=True)
|
|
49
51
|
_AnyNumber_co = TypeVar('_AnyNumber_co', number, Number, covariant=True)
|
|
50
52
|
|
|
51
|
-
type ArrayReducerFunc[_SCT: generic] = Callable[
|
|
52
|
-
|
|
53
|
+
type ArrayReducerFunc[_SCT: generic] = Callable[
|
|
54
|
+
Concatenate[_ArrayLike[_SCT], _P], NDArray[_SCT]
|
|
55
|
+
]
|
|
56
|
+
type ShapedNDArray[_Shape: tuple[int, ...], _SCT = generic] = ndarray[
|
|
57
|
+
_Shape, dtype[_SCT]
|
|
58
|
+
]
|
|
53
59
|
type MatrixLike[_SCT: generic] = ShapedNDArray[TupleOf2[int], _SCT]
|
|
54
60
|
type SquareMatrix[_I: int, _SCT: generic] = ShapedNDArray[TupleOf2[_I], _SCT]
|
|
55
|
-
type GlyphArray[_SCT: generic] = SquareMatrix[
|
|
61
|
+
type GlyphArray[_SCT: generic] = SquareMatrix[L[24], _SCT]
|
|
56
62
|
type TupleOf2[_T] = tuple[_T, _T]
|
|
57
63
|
type TupleOf3[_T] = tuple[_T, _T, _T]
|
|
58
64
|
|
|
@@ -64,25 +70,45 @@ GlyphBitmask: TypeAlias = GlyphArray[bool]
|
|
|
64
70
|
Bitmask: TypeAlias = MatrixLike[bool]
|
|
65
71
|
GreyscaleGlyphArray: TypeAlias = GlyphArray[float64]
|
|
66
72
|
GreyscaleArray: TypeAlias = MatrixLike[float64]
|
|
67
|
-
RGBArray: TypeAlias = ShapedNDArray[tuple[int, int,
|
|
68
|
-
RGBPixel: TypeAlias = ShapedNDArray[tuple[
|
|
73
|
+
RGBArray: TypeAlias = ShapedNDArray[tuple[int, int, L[3]], uint8]
|
|
74
|
+
RGBPixel: TypeAlias = ShapedNDArray[tuple[L[3]], uint8]
|
|
69
75
|
|
|
70
76
|
RGBImageLike: TypeAlias = Image | RGBArray
|
|
71
|
-
RGBVectorLike: TypeAlias =
|
|
72
|
-
ColorDictKeys =
|
|
73
|
-
Ansi4BitAlias =
|
|
74
|
-
Ansi8BitAlias =
|
|
75
|
-
Ansi24BitAlias =
|
|
77
|
+
RGBVectorLike: TypeAlias = IntSequence | RGBPixel
|
|
78
|
+
ColorDictKeys = L['fg', 'bg']
|
|
79
|
+
Ansi4BitAlias = L['4b']
|
|
80
|
+
Ansi8BitAlias = L['8b']
|
|
81
|
+
Ansi24BitAlias = L['24b']
|
|
76
82
|
AnsiColorAlias = Ansi4BitAlias | Ansi8BitAlias | Ansi24BitAlias
|
|
77
|
-
FontArgType: TypeAlias = FreeTypeFont | UserFont | str
|
|
83
|
+
FontArgType: TypeAlias = 'FreeTypeFont | UserFont | str'
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def eval_annotation(annotation: str, **kwargs) -> Any:
|
|
87
|
+
globals_ = kwargs.get('globals', {}) | globals()
|
|
88
|
+
locals_ = kwargs.get('locals', {})
|
|
89
|
+
try:
|
|
90
|
+
return subtype(eval(annotation, globals_.copy(), locals_.copy()))
|
|
91
|
+
except NameError as e:
|
|
92
|
+
try:
|
|
93
|
+
import typing
|
|
94
|
+
|
|
95
|
+
globals_[e.name] = getattr(typing, e.name)
|
|
96
|
+
return eval_annotation(annotation, globals=globals_, locals=locals_)
|
|
97
|
+
except AttributeError:
|
|
98
|
+
pass
|
|
99
|
+
raise
|
|
78
100
|
|
|
79
101
|
|
|
80
102
|
def type_error_msg(err_obj, *expected, context: str = '', obj_repr=False):
|
|
81
103
|
n_expected = len(expected)
|
|
82
|
-
name_slots = [
|
|
83
|
-
if
|
|
104
|
+
name_slots = ["{%d.__name__!r}" % n for n in range(n_expected)]
|
|
105
|
+
if n_expected > 1:
|
|
84
106
|
name_slots[-1] = f"or {name_slots[-1]}"
|
|
85
|
-
names = (
|
|
107
|
+
names = (
|
|
108
|
+
(', ' if n_expected > 2 else ' ')
|
|
109
|
+
.join([context.strip(), *name_slots])
|
|
110
|
+
.format(*expected)
|
|
111
|
+
)
|
|
86
112
|
if not obj_repr:
|
|
87
113
|
if not isinstance(err_obj, type):
|
|
88
114
|
err_obj = type(err_obj)
|
|
@@ -100,11 +126,14 @@ def is_matching_type(value, typ):
|
|
|
100
126
|
origin, args = deconstruct_type(typ)
|
|
101
127
|
if origin is Union:
|
|
102
128
|
return any(is_matching_type(value, arg) for arg in args)
|
|
103
|
-
elif origin is
|
|
129
|
+
elif origin is L:
|
|
104
130
|
return value in args
|
|
105
131
|
elif isinstance(typ, TypeVar):
|
|
106
132
|
if typ.__constraints__:
|
|
107
|
-
return any(
|
|
133
|
+
return any(
|
|
134
|
+
is_matching_type(value, constraint)
|
|
135
|
+
for constraint in typ.__constraints__
|
|
136
|
+
)
|
|
108
137
|
else:
|
|
109
138
|
return True
|
|
110
139
|
elif origin is type:
|
|
@@ -162,7 +191,9 @@ def is_matching_typed_dict(__d: dict, typed_dict: type[dict]) -> tuple[bool, str
|
|
|
162
191
|
field = __d.get(name)
|
|
163
192
|
if field is None or is_matching_type(field, typ):
|
|
164
193
|
continue
|
|
165
|
-
return False, type_error_msg(
|
|
194
|
+
return False, type_error_msg(
|
|
195
|
+
field, typ, context=f'keyword argument {name!r} of type'
|
|
196
|
+
)
|
|
166
197
|
return True, ''
|
|
167
198
|
|
|
168
199
|
|
|
@@ -188,20 +219,20 @@ def unionize(__iterable: Iterable[SupportsUnion[_T_contra, _T_co]]) -> _T_co:
|
|
|
188
219
|
|
|
189
220
|
_GenericAlias = type(Type[...]) | types.GenericAlias
|
|
190
221
|
_UnionGenericType = type(Union[..., None])
|
|
191
|
-
_LiteralGenericType = type(
|
|
222
|
+
_LiteralGenericType = type(L[''])
|
|
192
223
|
_CallableGenericType = type(Callable[[], ...]) | type(ABC_Callable[[], ...])
|
|
193
224
|
_CallableType = type(Callable) | ABC_Callable
|
|
194
225
|
|
|
195
226
|
|
|
196
227
|
class _BoundedDict[_KT, _VT](OrderedDict[_KT, _VT]):
|
|
228
|
+
"""Bounded OrderedDict, mimics FIFO behavior of `functools.lru_cache`"""
|
|
197
229
|
|
|
198
230
|
def __init__(self, *, maxsize: Optional[int] = 128):
|
|
199
|
-
"""Bounded OrderedDict, mimics FIFO behavior of `functools.lru_cache`"""
|
|
200
231
|
super().__init__()
|
|
201
232
|
if maxsize is not None:
|
|
202
233
|
maxsize = max(maxsize, 0)
|
|
203
234
|
|
|
204
|
-
@wraps(
|
|
235
|
+
@wraps(_BoundedDict.__setitem__)
|
|
205
236
|
def _fifo(_, key, value):
|
|
206
237
|
if maxsize <= len(self):
|
|
207
238
|
self.popitem(last=True)
|
|
@@ -213,9 +244,9 @@ class _BoundedDict[_KT, _VT](OrderedDict[_KT, _VT]):
|
|
|
213
244
|
|
|
214
245
|
|
|
215
246
|
_SUBTYPE_CACHE: _BoundedDict[int, ...] = _BoundedDict()
|
|
216
|
-
_ATTR_GETTERS: _BoundedDict[
|
|
217
|
-
|
|
218
|
-
)
|
|
247
|
+
_ATTR_GETTERS: _BoundedDict[
|
|
248
|
+
..., tuple[Callable[[Iterable], NamedTuple], op.attrgetter]
|
|
249
|
+
] = _BoundedDict()
|
|
219
250
|
|
|
220
251
|
|
|
221
252
|
def _unique_attrs(obj) -> Optional['NamedTuple']:
|
|
@@ -260,7 +291,8 @@ def _sort_attrs(obj, tp_name, attr_names):
|
|
|
260
291
|
try:
|
|
261
292
|
sig = inspect.signature(type(obj))
|
|
262
293
|
indices = (
|
|
263
|
-
dict.fromkeys(field_names, inf)
|
|
294
|
+
dict.fromkeys(field_names, inf)
|
|
295
|
+
| {p: i for i, p in enumerate(sig.parameters)}
|
|
264
296
|
).values()
|
|
265
297
|
for names in (attr_names, field_names):
|
|
266
298
|
names.sort(key=dict(zip(names, indices)).__getitem__)
|
|
@@ -282,7 +314,9 @@ def _sort_attrs(obj, tp_name, attr_names):
|
|
|
282
314
|
while sig_start not in line:
|
|
283
315
|
line = next(lines)
|
|
284
316
|
_, _, params = line.partition(sig_start)
|
|
285
|
-
params, _, _ = (
|
|
317
|
+
params, _, _ = (
|
|
318
|
+
s.translate(no_square_parens) for s in params.partition(')')
|
|
319
|
+
)
|
|
286
320
|
maybe_sigs.add(params)
|
|
287
321
|
except StopIteration:
|
|
288
322
|
break
|
|
@@ -290,15 +324,20 @@ def _sort_attrs(obj, tp_name, attr_names):
|
|
|
290
324
|
if maybe_sigs:
|
|
291
325
|
if len(maybe_sigs) > 1:
|
|
292
326
|
sig = max(
|
|
293
|
-
maybe_sigs,
|
|
327
|
+
maybe_sigs,
|
|
328
|
+
key=lambda s: sum(1 for sub in s.split(', ') if sub in field_names),
|
|
294
329
|
)
|
|
295
330
|
else:
|
|
296
331
|
sig = maybe_sigs.pop()
|
|
297
332
|
positions = {x: i for i, x in enumerate(sig.split(', ')) if x}
|
|
298
333
|
sorted_field_names = sorted(field_names, key=lambda k: positions.get(k, inf))
|
|
299
|
-
transitions = {
|
|
334
|
+
transitions = {
|
|
335
|
+
idx: sorted_field_names.index(x) for idx, x in enumerate(field_names)
|
|
336
|
+
}
|
|
300
337
|
field_names = sorted_field_names
|
|
301
|
-
attr_names = [
|
|
338
|
+
attr_names = [
|
|
339
|
+
attr_names[k] for k in map(transitions.__getitem__, range(len(attr_names)))
|
|
340
|
+
]
|
|
302
341
|
for Names in (attr_names, field_names):
|
|
303
342
|
name_attr = next((s for s in Names if s.strip('_') == 'name'), None)
|
|
304
343
|
if name_attr is not None:
|
|
@@ -335,7 +374,9 @@ def subtype[_T](typ: _T) -> _T:
|
|
|
335
374
|
args_list = list(args)
|
|
336
375
|
if (
|
|
337
376
|
literals := [
|
|
338
|
-
idx
|
|
377
|
+
idx
|
|
378
|
+
for idx, elem in enumerate(args)
|
|
379
|
+
if isinstance(elem, _LiteralGenericType)
|
|
339
380
|
]
|
|
340
381
|
) and len(literals) > 1:
|
|
341
382
|
|
|
@@ -345,7 +386,7 @@ def subtype[_T](typ: _T) -> _T:
|
|
|
345
386
|
return getattr(value, '__args__', ())
|
|
346
387
|
|
|
347
388
|
start = args[literals.pop(0)]
|
|
348
|
-
args_list[args_list.index(start)] =
|
|
389
|
+
args_list[args_list.index(start)] = L[
|
|
349
390
|
*start.__args__,
|
|
350
391
|
*dict.fromkeys(arg for idx in literals for arg in _next_args(idx)),
|
|
351
392
|
]
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'ANSI_4BIT_RGB',
|
|
3
|
+
'Back',
|
|
4
|
+
'CSI',
|
|
5
|
+
'Color',
|
|
6
|
+
'ColorNamespace',
|
|
7
|
+
'ColorStr',
|
|
8
|
+
'Fore',
|
|
9
|
+
'SGR_RESET',
|
|
10
|
+
'SgrParameter',
|
|
11
|
+
'SgrSequence',
|
|
12
|
+
'Style',
|
|
13
|
+
'ansi_4bit_to_rgb',
|
|
14
|
+
'ansi_8bit_to_rgb',
|
|
15
|
+
'ansicolor24Bit',
|
|
16
|
+
'ansicolor4Bit',
|
|
17
|
+
'ansicolor8Bit',
|
|
18
|
+
'color_chain',
|
|
19
|
+
'colorbytes',
|
|
20
|
+
'get_ansi_type',
|
|
21
|
+
'int2rgb',
|
|
22
|
+
'hexstr2rgb',
|
|
23
|
+
'hsl2rgb',
|
|
24
|
+
'hsv2rgb',
|
|
25
|
+
'is_u24',
|
|
26
|
+
'lab2rgb',
|
|
27
|
+
'lab2xyz',
|
|
28
|
+
'named_color',
|
|
29
|
+
'nearest_ansi_4bit_rgb',
|
|
30
|
+
'nearest_ansi_8bit_rgb',
|
|
31
|
+
'randcolor',
|
|
32
|
+
'rgb2ansi_escape',
|
|
33
|
+
'rgb2int',
|
|
34
|
+
'rgb2hexstr',
|
|
35
|
+
'rgb2hsl',
|
|
36
|
+
'rgb2hsv',
|
|
37
|
+
'rgb2lab',
|
|
38
|
+
'rgb2xyz',
|
|
39
|
+
'rgb_diff',
|
|
40
|
+
'rgb_dispatch',
|
|
41
|
+
'rgb_to_ansi_8bit',
|
|
42
|
+
'xyz2lab',
|
|
43
|
+
'xyz2rgb',
|
|
44
|
+
]
|
|
45
|
+
from . import colorconv, core, iterators, palette
|
|
46
|
+
from .colorconv import (
|
|
47
|
+
ANSI_4BIT_RGB,
|
|
48
|
+
ansi_4bit_to_rgb,
|
|
49
|
+
ansi_8bit_to_rgb,
|
|
50
|
+
int2rgb,
|
|
51
|
+
hexstr2rgb,
|
|
52
|
+
hsl2rgb,
|
|
53
|
+
hsv2rgb,
|
|
54
|
+
is_u24,
|
|
55
|
+
lab2rgb,
|
|
56
|
+
lab2xyz,
|
|
57
|
+
nearest_ansi_4bit_rgb,
|
|
58
|
+
nearest_ansi_8bit_rgb,
|
|
59
|
+
rgb2int,
|
|
60
|
+
rgb2hexstr,
|
|
61
|
+
rgb2hsl,
|
|
62
|
+
rgb2hsv,
|
|
63
|
+
rgb2lab,
|
|
64
|
+
rgb2xyz,
|
|
65
|
+
rgb_diff,
|
|
66
|
+
rgb_to_ansi_8bit,
|
|
67
|
+
xyz2lab,
|
|
68
|
+
xyz2rgb,
|
|
69
|
+
)
|
|
70
|
+
from .core import (
|
|
71
|
+
CSI,
|
|
72
|
+
Color,
|
|
73
|
+
ColorStr,
|
|
74
|
+
SGR_RESET,
|
|
75
|
+
SgrParameter,
|
|
76
|
+
SgrSequence,
|
|
77
|
+
ansicolor24Bit,
|
|
78
|
+
ansicolor4Bit,
|
|
79
|
+
ansicolor8Bit,
|
|
80
|
+
color_chain,
|
|
81
|
+
colorbytes,
|
|
82
|
+
get_ansi_type,
|
|
83
|
+
randcolor,
|
|
84
|
+
rgb2ansi_escape,
|
|
85
|
+
)
|
|
86
|
+
from .palette import Back, ColorNamespace, Fore, Style, named_color, rgb_dispatch
|
|
@@ -2,16 +2,16 @@ __all__ = [
|
|
|
2
2
|
'ANSI_4BIT_RGB',
|
|
3
3
|
'ansi_4bit_to_rgb',
|
|
4
4
|
'ansi_8bit_to_rgb',
|
|
5
|
-
'
|
|
5
|
+
'int2rgb',
|
|
6
6
|
'hexstr2rgb',
|
|
7
7
|
'hsl2rgb',
|
|
8
8
|
'hsv2rgb',
|
|
9
|
-
'
|
|
9
|
+
'is_u24',
|
|
10
10
|
'lab2rgb',
|
|
11
11
|
'lab2xyz',
|
|
12
12
|
'nearest_ansi_4bit_rgb',
|
|
13
13
|
'nearest_ansi_8bit_rgb',
|
|
14
|
-
'
|
|
14
|
+
'rgb2int',
|
|
15
15
|
'rgb2hexstr',
|
|
16
16
|
'rgb2hsl',
|
|
17
17
|
'rgb2hsv',
|
|
@@ -23,40 +23,76 @@ __all__ = [
|
|
|
23
23
|
'xyz2rgb',
|
|
24
24
|
]
|
|
25
25
|
|
|
26
|
+
from functools import lru_cache
|
|
26
27
|
from operator import mul, truediv
|
|
27
|
-
from typing import Final, Literal, SupportsInt,
|
|
28
|
+
from typing import Final, Literal, SupportsInt, TypeGuard
|
|
28
29
|
|
|
29
30
|
import numpy as np
|
|
30
31
|
|
|
31
|
-
from .._typing import
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
from .._typing import (
|
|
33
|
+
Float3Tuple,
|
|
34
|
+
FloatSequence,
|
|
35
|
+
Int3Tuple,
|
|
36
|
+
RGBPixel,
|
|
37
|
+
RGBVectorLike,
|
|
38
|
+
ShapedNDArray,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@lru_cache
|
|
43
|
+
def _supports_int(typ: type) -> TypeGuard[type[SupportsInt]]:
|
|
44
|
+
return issubclass(typ, SupportsInt)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def is_u24(value, *, strict: bool = False):
|
|
48
|
+
"""Check if value is an unsigned 24-bit integer.
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
---------
|
|
52
|
+
value
|
|
53
|
+
Input number
|
|
54
|
+
strict : bool
|
|
55
|
+
Whether to return False or raise ValueError on failure
|
|
56
|
+
|
|
57
|
+
Raises
|
|
58
|
+
------
|
|
59
|
+
ValueError
|
|
60
|
+
Raised when `strict=True` and value is not u24
|
|
61
|
+
"""
|
|
62
|
+
if _supports_int(type(value)):
|
|
63
|
+
if 0 <= int(value) <= 0xFFFFFF:
|
|
37
64
|
return True
|
|
38
65
|
elif not strict:
|
|
39
66
|
return False
|
|
40
|
-
raise
|
|
67
|
+
raise ValueError(f"{value!r} is not u24")
|
|
41
68
|
|
|
42
69
|
|
|
43
70
|
def hexstr2rgb(__str: str) -> Int3Tuple:
|
|
44
|
-
|
|
45
|
-
|
|
71
|
+
n = len(__str)
|
|
72
|
+
if n % 4 == 0: # trunc alpha
|
|
73
|
+
n *= 3
|
|
74
|
+
n //= 4
|
|
75
|
+
__str = __str[:n]
|
|
76
|
+
if n == 3: # rgb -> rrggbb
|
|
77
|
+
__str = ''.join(c * 2 for c in __str)
|
|
78
|
+
if is_u24(value := int(__str, 16), strict=True):
|
|
79
|
+
return int2rgb(value)
|
|
46
80
|
|
|
47
81
|
|
|
48
82
|
def rgb2hexstr(rgb: RGBVectorLike) -> str:
|
|
49
|
-
|
|
50
|
-
return f'{r:02x}{g:02x}{b:02x}'
|
|
83
|
+
return "%02x%02x%02x" % tuple(rgb)
|
|
51
84
|
|
|
52
85
|
|
|
53
|
-
def
|
|
86
|
+
def rgb2int(rgb: RGBVectorLike) -> int:
|
|
54
87
|
r, g, b = map(int, rgb)
|
|
55
88
|
return r << 16 | g << 8 | b
|
|
56
89
|
|
|
57
90
|
|
|
58
|
-
def
|
|
59
|
-
|
|
91
|
+
def int2rgb(__x: int) -> Int3Tuple:
|
|
92
|
+
try:
|
|
93
|
+
return getattr(__x, 'rgb')
|
|
94
|
+
except AttributeError:
|
|
95
|
+
return (__x >> 16) & 0xFF, (__x >> 8) & 0xFF, __x & 0xFF
|
|
60
96
|
|
|
61
97
|
|
|
62
98
|
def xyz2lab(xyz: FloatSequence) -> Float3Tuple:
|
|
@@ -78,7 +114,10 @@ def lab2xyz(lab: FloatSequence) -> Float3Tuple:
|
|
|
78
114
|
x, y, z = map(
|
|
79
115
|
mul,
|
|
80
116
|
(95.047, 100.0, 108.883),
|
|
81
|
-
map(
|
|
117
|
+
map(
|
|
118
|
+
lambda i: (lambda j: j if j > 0.008856 else (i - 16 / 116) / 7.787)(i**3),
|
|
119
|
+
(x, y, z),
|
|
120
|
+
),
|
|
82
121
|
)
|
|
83
122
|
return x, y, z
|
|
84
123
|
|
|
@@ -98,47 +137,44 @@ def rgb2xyz(rgb: RGBPixel) -> Float3Tuple:
|
|
|
98
137
|
|
|
99
138
|
|
|
100
139
|
def xyz2rgb(xyz: ShapedNDArray[tuple[Literal[3]], np.float64]) -> Int3Tuple:
|
|
101
|
-
r, g, b = (
|
|
140
|
+
r, g, b = (
|
|
141
|
+
np.clip(M_XYZ2RGB @ np.array(xyz, dtype=np.float64), 0.0, 1.0) * 255.0
|
|
142
|
+
).astype(int)
|
|
102
143
|
return r, g, b
|
|
103
144
|
|
|
104
145
|
|
|
105
146
|
def hsl2rgb(hsl: FloatSequence) -> Int3Tuple:
|
|
106
147
|
h, s, L = hsl
|
|
107
|
-
h
|
|
148
|
+
h /= 360
|
|
149
|
+
h %= 1
|
|
108
150
|
if h < 0:
|
|
109
151
|
h += 1
|
|
110
|
-
r = g = b = L
|
|
111
152
|
v = (L * (1.0 + s)) if L <= 0.5 else (L + s - L * s)
|
|
112
|
-
if v > 0:
|
|
153
|
+
if v > 0 and 0 <= (sextant := int(h)) <= 5:
|
|
113
154
|
m = L + L - v
|
|
114
155
|
sv = (v - m) / v
|
|
115
156
|
h *= 6.0
|
|
116
|
-
|
|
117
|
-
fract = h - sextant
|
|
118
|
-
vsf = v * sv * fract
|
|
157
|
+
vsf = v * sv * (h - sextant)
|
|
119
158
|
mid1 = m + vsf
|
|
120
159
|
mid2 = v - vsf
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
elif sextant == 5:
|
|
132
|
-
r, g, b = v, m, mid2
|
|
133
|
-
r, g, b = (round(x * 255) for x in (r, g, b))
|
|
160
|
+
r, g, b = (
|
|
161
|
+
round(x * 0xFF)
|
|
162
|
+
for x in [
|
|
163
|
+
[v, mid1, m],
|
|
164
|
+
[mid2, v, m],
|
|
165
|
+
[m, v, mid1],
|
|
166
|
+
[m, mid2, v],
|
|
167
|
+
[mid1, m, v],
|
|
168
|
+
][sextant]
|
|
169
|
+
)
|
|
134
170
|
else:
|
|
135
|
-
r, g, b =
|
|
171
|
+
r, g, b = [round(L * 0xFF)] * 3
|
|
136
172
|
return r, g, b
|
|
137
173
|
|
|
138
174
|
|
|
139
175
|
def rgb2hsl(rgb: RGBVectorLike) -> Float3Tuple:
|
|
140
176
|
r, g, b = (x / 255.0 for x in rgb)
|
|
141
|
-
m, v = sorted([r, g, b])
|
|
177
|
+
m, _, v = sorted([r, g, b])
|
|
142
178
|
L = (m + v) / 2
|
|
143
179
|
h = s = 0
|
|
144
180
|
if L > 0:
|
|
@@ -245,7 +281,7 @@ def ansi_4bit_to_rgb(value: int):
|
|
|
245
281
|
return ANSI_4BIT_RGB[value]
|
|
246
282
|
|
|
247
283
|
|
|
248
|
-
def _4b_lookup():
|
|
284
|
+
def _4b_lookup() -> dict[Int3Tuple, Int3Tuple]:
|
|
249
285
|
def rgb_dist(rgb, ansi):
|
|
250
286
|
r_mean = (rgb[:, 0:1] + ansi[:, 0]) / 2
|
|
251
287
|
r_diff = (rgb[:, 0:1] - ansi[:, 0]) * (2 + r_mean / 256)
|
|
@@ -255,14 +291,15 @@ def _4b_lookup():
|
|
|
255
291
|
|
|
256
292
|
rgb_4b_arr = np.asarray(ANSI_4BIT_RGB)
|
|
257
293
|
quants = np.stack(
|
|
258
|
-
np.meshgrid(*np.repeat(np.arange(32).reshape([1, -1]), 3, 0), indexing='ij'),
|
|
294
|
+
np.meshgrid(*np.repeat(np.arange(32).reshape([1, -1]), 3, 0), indexing='ij'),
|
|
295
|
+
axis=-1,
|
|
259
296
|
).reshape([-1, 3])
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
297
|
+
nearest_colors = rgb_4b_arr[np.argmin(rgb_dist(quants * 8, rgb_4b_arr), axis=1)]
|
|
298
|
+
table: dict = {
|
|
299
|
+
tuple(map(int, color)): tuple(map(int, nearest_colors[i]))
|
|
300
|
+
for i, color in enumerate(quants)
|
|
264
301
|
}
|
|
265
|
-
return
|
|
302
|
+
return table
|
|
266
303
|
|
|
267
304
|
|
|
268
305
|
ANSI_4BIT_RGB_MAP = _4b_lookup()
|