chromatic-python 0.2.0__py3-none-any.whl → 0.2.2__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.
- chromatic/__init__.py +67 -0
- chromatic/_typing.py +8 -5
- chromatic/_version.py +9 -4
- chromatic/ascii/_array.py +71 -35
- chromatic/color/core.py +301 -262
- chromatic/color/core.pyi +65 -56
- chromatic/color/iterators.py +166 -0
- chromatic/color/palette.py +83 -208
- chromatic/color/palette.pyi +16 -51
- chromatic/data/__init__.py +5 -15
- chromatic/demo.py +7 -7
- {chromatic_python-0.2.0.dist-info → chromatic_python-0.2.2.dist-info}/METADATA +10 -9
- chromatic_python-0.2.2.dist-info/RECORD +28 -0
- {chromatic_python-0.2.0.dist-info → chromatic_python-0.2.2.dist-info}/WHEEL +1 -1
- chromatic_python-0.2.0.dist-info/RECORD +0 -27
- {chromatic_python-0.2.0.dist-info → chromatic_python-0.2.2.dist-info/licenses}/LICENSE +0 -0
- {chromatic_python-0.2.0.dist-info → chromatic_python-0.2.2.dist-info}/top_level.txt +0 -0
chromatic/__init__.py
CHANGED
|
@@ -7,6 +7,7 @@ from . import ascii, color, data
|
|
|
7
7
|
from .ascii import (
|
|
8
8
|
AnsiImage,
|
|
9
9
|
ansi2img,
|
|
10
|
+
ansify,
|
|
10
11
|
ansi_quantize,
|
|
11
12
|
ascii2img,
|
|
12
13
|
ascii_printable,
|
|
@@ -40,3 +41,69 @@ from .color import (
|
|
|
40
41
|
from .data import register_user_font
|
|
41
42
|
|
|
42
43
|
__all__ = []
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
import os
|
|
47
|
+
import sys
|
|
48
|
+
from functools import lru_cache, wraps
|
|
49
|
+
from types import ModuleType
|
|
50
|
+
|
|
51
|
+
def find_modules(path: str):
|
|
52
|
+
from setuptools import find_packages
|
|
53
|
+
from pkgutil import iter_modules
|
|
54
|
+
|
|
55
|
+
tree: dict[str, dict | ModuleType] = {__name__: {'__module__': sys.modules[__name__]}}
|
|
56
|
+
children = set()
|
|
57
|
+
for pkg in find_packages(path):
|
|
58
|
+
children.add(pkg)
|
|
59
|
+
pkg_path = path + '/' + pkg.replace('.', '/')
|
|
60
|
+
if sys.version_info.major == 2 or (
|
|
61
|
+
sys.version_info.major == 3 and sys.version_info.minor < 6
|
|
62
|
+
):
|
|
63
|
+
for _, name, ispkg in iter_modules([pkg_path]):
|
|
64
|
+
if not ispkg:
|
|
65
|
+
children.add(f"{pkg}.{name}")
|
|
66
|
+
else:
|
|
67
|
+
for info in iter_modules([pkg_path]):
|
|
68
|
+
if not info.ispkg:
|
|
69
|
+
children.add(f"{pkg}.{info.name}")
|
|
70
|
+
for child in children:
|
|
71
|
+
name = f"{__name__}.{child}"
|
|
72
|
+
depth = tree
|
|
73
|
+
for node in name.split('.'):
|
|
74
|
+
if node not in depth:
|
|
75
|
+
depth[node] = {}
|
|
76
|
+
depth = depth[node]
|
|
77
|
+
if name in sys.modules:
|
|
78
|
+
depth['__module__'] = sys.modules[name]
|
|
79
|
+
return tree
|
|
80
|
+
|
|
81
|
+
def publicize_modules(modulename: str, tree: dict[str, dict | ModuleType]):
|
|
82
|
+
def is_local(obj: object):
|
|
83
|
+
if isinstance(obj, ModuleType):
|
|
84
|
+
return obj.__spec__.parent == modulename
|
|
85
|
+
elif hasattr(obj, '__module__'):
|
|
86
|
+
return obj.__module__ == modulename
|
|
87
|
+
return obj is not None
|
|
88
|
+
|
|
89
|
+
for name, subtree in tree.items():
|
|
90
|
+
if name == '__module__' and isinstance(subtree, ModuleType):
|
|
91
|
+
submodule = subtree
|
|
92
|
+
init_dir = dir(submodule)
|
|
93
|
+
|
|
94
|
+
@wraps(submodule.__dir__)
|
|
95
|
+
def wrapped():
|
|
96
|
+
s = set(attr for attr in init_dir if is_local(getattr(submodule, attr, None)))
|
|
97
|
+
if hasattr(submodule, '__all__'):
|
|
98
|
+
s.update(submodule.__all__)
|
|
99
|
+
return list(s)
|
|
100
|
+
|
|
101
|
+
sys.modules[submodule.__name__].__dir__ = wrapped
|
|
102
|
+
|
|
103
|
+
else:
|
|
104
|
+
publicize_modules(f"{modulename}.{name}", subtree)
|
|
105
|
+
|
|
106
|
+
publicize_modules(*find_modules(os.path.split(__file__)[0]).popitem())
|
|
107
|
+
|
|
108
|
+
finally:
|
|
109
|
+
del find_modules, publicize_modules
|
chromatic/_typing.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
|
+
import operator as op
|
|
4
5
|
import re
|
|
5
6
|
import types
|
|
6
7
|
from collections import OrderedDict, namedtuple
|
|
7
8
|
from collections.abc import Callable as ABC_Callable
|
|
8
9
|
from functools import reduce, wraps
|
|
9
10
|
from numbers import Number
|
|
10
|
-
from operator import attrgetter, or_ as bitwise_or
|
|
11
11
|
from typing import (
|
|
12
12
|
Any,
|
|
13
13
|
Callable,
|
|
@@ -79,7 +79,7 @@ FontArgType: TypeAlias = FreeTypeFont | UserFont | str | int
|
|
|
79
79
|
|
|
80
80
|
def type_error_msg(err_obj, *expected, context: str = '', obj_repr=False):
|
|
81
81
|
n_expected = len(expected)
|
|
82
|
-
name_slots = [
|
|
82
|
+
name_slots = ['{%d.__qualname__!r}' % n for n in range(n_expected)]
|
|
83
83
|
if name_slots and n_expected > 1:
|
|
84
84
|
name_slots[-1] = f"or {name_slots[-1]}"
|
|
85
85
|
names = (', ' if n_expected > 2 else ' ').join([context.strip()] + name_slots).format(*expected)
|
|
@@ -183,7 +183,7 @@ class SupportsUnion(Protocol[_T_contra, _T_co]):
|
|
|
183
183
|
|
|
184
184
|
|
|
185
185
|
def unionize(__iterable: Iterable[SupportsUnion[_T_contra, _T_co]]) -> _T_co:
|
|
186
|
-
return reduce(
|
|
186
|
+
return reduce(op.or_, __iterable)
|
|
187
187
|
|
|
188
188
|
|
|
189
189
|
_GenericAlias = type(Type[...]) | types.GenericAlias
|
|
@@ -213,7 +213,7 @@ class _BoundedDict[_KT, _VT](OrderedDict[_KT, _VT]):
|
|
|
213
213
|
|
|
214
214
|
|
|
215
215
|
_SUBTYPE_CACHE: _BoundedDict[int, ...] = _BoundedDict()
|
|
216
|
-
_ATTR_GETTERS: _BoundedDict[..., tuple[Callable[[Iterable], NamedTuple], attrgetter]] = (
|
|
216
|
+
_ATTR_GETTERS: _BoundedDict[..., tuple[Callable[[Iterable], NamedTuple], op.attrgetter]] = (
|
|
217
217
|
_BoundedDict()
|
|
218
218
|
)
|
|
219
219
|
|
|
@@ -247,7 +247,10 @@ def _unique_attrs(obj) -> Optional['NamedTuple']:
|
|
|
247
247
|
+ 'Attrs'
|
|
248
248
|
).strip('_')
|
|
249
249
|
UniqueAttrs = cast(NamedTuple, namedtuple(tup_name, field_names))
|
|
250
|
-
_ATTR_GETTERS[tp] = constructor, getter =
|
|
250
|
+
_ATTR_GETTERS[tp] = [constructor, getter] = [
|
|
251
|
+
UniqueAttrs._make,
|
|
252
|
+
op.attrgetter(*attr_names), # noqa
|
|
253
|
+
]
|
|
251
254
|
return constructor(getter(obj))
|
|
252
255
|
|
|
253
256
|
|
chromatic/_version.py
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
# file generated by
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
3
6
|
TYPE_CHECKING = False
|
|
4
7
|
if TYPE_CHECKING:
|
|
5
|
-
from typing import Tuple
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
6
11
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
12
|
else:
|
|
8
13
|
VERSION_TUPLE = object
|
|
@@ -12,5 +17,5 @@ __version__: str
|
|
|
12
17
|
__version_tuple__: VERSION_TUPLE
|
|
13
18
|
version_tuple: VERSION_TUPLE
|
|
14
19
|
|
|
15
|
-
__version__ = version = '0.2.
|
|
16
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
20
|
+
__version__ = version = '0.2.2'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 2, 2)
|
chromatic/ascii/_array.py
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
__all__ = [
|
|
4
|
+
'ansi2img',
|
|
5
|
+
'ansify',
|
|
6
|
+
'ansi_quantize',
|
|
7
|
+
'ascii2img',
|
|
8
|
+
'contrast_stretch',
|
|
9
|
+
'equalize_white_point',
|
|
10
|
+
'get_font_key',
|
|
11
|
+
'get_font_object',
|
|
12
|
+
'img2ansi',
|
|
13
|
+
'img2ascii',
|
|
14
|
+
'read_ans',
|
|
15
|
+
'render_ans',
|
|
16
|
+
'render_font_char',
|
|
17
|
+
'render_font_str',
|
|
18
|
+
'reshape_ansi',
|
|
19
|
+
'scale_saturation',
|
|
20
|
+
'shuffle_char_set',
|
|
21
|
+
'_scaled_hu_moments',
|
|
22
|
+
'to_sgr_array',
|
|
23
|
+
'AnsiImage',
|
|
24
|
+
'_otsu_mask',
|
|
25
|
+
]
|
|
26
|
+
|
|
3
27
|
import math
|
|
4
28
|
import os.path
|
|
5
29
|
import random
|
|
@@ -62,29 +86,6 @@ from ..data import UserFont
|
|
|
62
86
|
if TYPE_CHECKING:
|
|
63
87
|
from _typeshed import AnyStr_co
|
|
64
88
|
|
|
65
|
-
__all__ = [
|
|
66
|
-
'ansi2img',
|
|
67
|
-
'ansi_quantize',
|
|
68
|
-
'ascii2img',
|
|
69
|
-
'contrast_stretch',
|
|
70
|
-
'equalize_white_point',
|
|
71
|
-
'get_font_key',
|
|
72
|
-
'get_font_object',
|
|
73
|
-
'img2ansi',
|
|
74
|
-
'img2ascii',
|
|
75
|
-
'read_ans',
|
|
76
|
-
'render_ans',
|
|
77
|
-
'render_font_char',
|
|
78
|
-
'render_font_str',
|
|
79
|
-
'reshape_ansi',
|
|
80
|
-
'scale_saturation',
|
|
81
|
-
'shuffle_char_set',
|
|
82
|
-
'_scaled_hu_moments',
|
|
83
|
-
'to_sgr_array',
|
|
84
|
-
'AnsiImage',
|
|
85
|
-
'_otsu_mask',
|
|
86
|
-
]
|
|
87
|
-
|
|
88
89
|
|
|
89
90
|
def get_font_key(font: FreeTypeFont):
|
|
90
91
|
"""Obtain a unique tuple pair from a FreeTypeFont object.
|
|
@@ -597,6 +598,7 @@ def img2ascii(
|
|
|
597
598
|
return ascii_str
|
|
598
599
|
|
|
599
600
|
|
|
601
|
+
@rgb_dispatch
|
|
600
602
|
def img2ansi(
|
|
601
603
|
__img: RGBImageLike | PathLike[str] | str,
|
|
602
604
|
__font: FontArgType = 'arial.ttf',
|
|
@@ -605,7 +607,7 @@ def img2ansi(
|
|
|
605
607
|
ansi_type: AnsiColorParam = DEFAULT_ANSI,
|
|
606
608
|
sort_glyphs: bool | type[reversed] = True,
|
|
607
609
|
equalize: bool | Literal['white_point'] = True,
|
|
608
|
-
bg: Color | Int3Tuple = (0, 0, 0),
|
|
610
|
+
bg: Color | Int3Tuple | str = (0, 0, 0),
|
|
609
611
|
):
|
|
610
612
|
"""Convert an image to an ANSI array.
|
|
611
613
|
|
|
@@ -665,7 +667,7 @@ def img2ansi(
|
|
|
665
667
|
"""
|
|
666
668
|
if ansi_type is not DEFAULT_ANSI:
|
|
667
669
|
ansi_type = get_ansi_type(ansi_type)
|
|
668
|
-
bg_wrapper = ColorStr('{}', color_spec={'bg': bg}, ansi_type=ansi_type,
|
|
670
|
+
bg_wrapper = ColorStr('{}', color_spec={'bg': bg}, ansi_type=ansi_type, reset=False)
|
|
669
671
|
base_ascii, color_arr = img2ascii(__img, __font, factor, char_set, sort_glyphs, ret_img=True)
|
|
670
672
|
lines = base_ascii.splitlines()
|
|
671
673
|
h, w = map(len, (lines, lines[0]))
|
|
@@ -697,7 +699,7 @@ def ascii2img(
|
|
|
697
699
|
font_size=24,
|
|
698
700
|
*,
|
|
699
701
|
fg: Int3Tuple | str = (0, 0, 0),
|
|
700
|
-
bg: Int3Tuple | str = (
|
|
702
|
+
bg: Int3Tuple | str = (0xFF, 0xFF, 0xFF),
|
|
701
703
|
):
|
|
702
704
|
"""Render a literal string as an image.
|
|
703
705
|
|
|
@@ -803,7 +805,7 @@ def ansi2img(
|
|
|
803
805
|
x_offset = 0
|
|
804
806
|
for color_str in row:
|
|
805
807
|
text_width = font.getbbox(color_str.base_str)[2]
|
|
806
|
-
if color_str
|
|
808
|
+
if getattr(color_str, '_sgr_').is_reset():
|
|
807
809
|
fg_default = None
|
|
808
810
|
bg_default = input_bg
|
|
809
811
|
if fg_color := getattr(color_str.fg, 'rgb', fg_default):
|
|
@@ -823,6 +825,38 @@ def ansi2img(
|
|
|
823
825
|
return img
|
|
824
826
|
|
|
825
827
|
|
|
828
|
+
def ansify(
|
|
829
|
+
__img: RGBImageLike | PathLike[str] | str,
|
|
830
|
+
/,
|
|
831
|
+
*,
|
|
832
|
+
font: FontArgType = UserFont.IBM_VGA_437_8X16,
|
|
833
|
+
font_size: int = 16,
|
|
834
|
+
factor: int = 200,
|
|
835
|
+
char_set: Iterable[str] = None,
|
|
836
|
+
sort_glyphs: bool | type[reversed] = True,
|
|
837
|
+
ansi_type: AnsiColorParam = DEFAULT_ANSI,
|
|
838
|
+
equalize: bool | Literal['white_point'] = True,
|
|
839
|
+
fg: Int3Tuple | str = (170, 170, 170),
|
|
840
|
+
bg: Int3Tuple | str | Literal['auto'] = (0, 0, 0),
|
|
841
|
+
):
|
|
842
|
+
return ansi2img(
|
|
843
|
+
img2ansi(
|
|
844
|
+
__img,
|
|
845
|
+
font,
|
|
846
|
+
factor=factor,
|
|
847
|
+
char_set=char_set,
|
|
848
|
+
ansi_type=ansi_type,
|
|
849
|
+
sort_glyphs=sort_glyphs,
|
|
850
|
+
equalize=equalize,
|
|
851
|
+
bg=bg,
|
|
852
|
+
),
|
|
853
|
+
font,
|
|
854
|
+
font_size=font_size,
|
|
855
|
+
fg_default=fg,
|
|
856
|
+
bg_default=bg,
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
|
|
826
860
|
def _is_array(__obj: Any) -> TypeGuard[ndarray]:
|
|
827
861
|
return isinstance(__obj, ndarray)
|
|
828
862
|
|
|
@@ -913,6 +947,7 @@ def to_sgr_array(__str: str, ansi_type: AnsiColorParam = DEFAULT_ANSI):
|
|
|
913
947
|
resets = frozenset(resets_btok)
|
|
914
948
|
pad_esc = {0x1B: '\x00\x1b'}
|
|
915
949
|
x = []
|
|
950
|
+
sgr: SgrSequence | None
|
|
916
951
|
for line in __str.splitlines():
|
|
917
952
|
xs = []
|
|
918
953
|
ansi_meta = {}
|
|
@@ -927,7 +962,7 @@ def to_sgr_array(__str: str, ansi_type: AnsiColorParam = DEFAULT_ANSI):
|
|
|
927
962
|
else:
|
|
928
963
|
cs = new_cs(s)
|
|
929
964
|
if sgr is None:
|
|
930
|
-
sgr = cs
|
|
965
|
+
sgr: SgrSequence = getattr(cs, '_sgr_')
|
|
931
966
|
if sgr.is_reset():
|
|
932
967
|
ansi_meta.clear()
|
|
933
968
|
for k in resets.intersection(sgr.values()):
|
|
@@ -947,7 +982,7 @@ def to_sgr_array(__str: str, ansi_type: AnsiColorParam = DEFAULT_ANSI):
|
|
|
947
982
|
if not ansi_meta.get('bright'):
|
|
948
983
|
ansi_meta['bright'] = True
|
|
949
984
|
new_sgr = SgrSequence(
|
|
950
|
-
sgr.values() + [p._value_ for p in prev
|
|
985
|
+
sgr.values() + [p._value_ for p in getattr(prev, '_sgr_') if p.is_color()]
|
|
951
986
|
)
|
|
952
987
|
prev = cs = new_cs(cs.base_str, new_sgr)
|
|
953
988
|
xs.append(cs.as_ansi_type(ansi_typ))
|
|
@@ -1009,9 +1044,10 @@ def read_ans[AnyStr: (str, bytes)](__path: PathLike[AnyStr] | AnyStr, encoding='
|
|
|
1009
1044
|
class AnsiImage:
|
|
1010
1045
|
|
|
1011
1046
|
@classmethod
|
|
1012
|
-
def open[
|
|
1013
|
-
|
|
1014
|
-
|
|
1047
|
+
def open[AnyStr: (
|
|
1048
|
+
str,
|
|
1049
|
+
bytes,
|
|
1050
|
+
)](
|
|
1015
1051
|
cls,
|
|
1016
1052
|
fp: PathLike[AnyStr] | AnyStr,
|
|
1017
1053
|
shape: TupleOf2[int],
|
|
@@ -1076,7 +1112,7 @@ class AnsiImage:
|
|
|
1076
1112
|
if not __table:
|
|
1077
1113
|
return self
|
|
1078
1114
|
table = {
|
|
1079
|
-
k:
|
|
1115
|
+
k: v if v not in frozenset(x for c in ' \t\n\r\v\f' for x in (c, ord(c))) else ' '
|
|
1080
1116
|
for (k, v) in __table.items()
|
|
1081
1117
|
if k != ord('\n')
|
|
1082
1118
|
}
|
|
@@ -1098,8 +1134,8 @@ class AnsiImage:
|
|
|
1098
1134
|
return getattr(self, attr_name)[0]
|
|
1099
1135
|
lines = []
|
|
1100
1136
|
for line in self.data:
|
|
1101
|
-
buffer = []
|
|
1102
1137
|
if line:
|
|
1138
|
+
buffer = []
|
|
1103
1139
|
initial = line[0]
|
|
1104
1140
|
for s in line[1:]:
|
|
1105
1141
|
if s.ansi_partition()[::2] == initial.ansi_partition()[::2]:
|
|
@@ -1109,7 +1145,7 @@ class AnsiImage:
|
|
|
1109
1145
|
initial = s
|
|
1110
1146
|
buffer.append(initial)
|
|
1111
1147
|
lines.append(''.join(buffer))
|
|
1112
|
-
setattr(self, attr_name, ('\n'.join(lines) + SGR_RESET, self.shape))
|
|
1148
|
+
setattr(self, attr_name, ('\n'.join(lines) + SGR_RESET.decode(), self.shape))
|
|
1113
1149
|
return getattr(self, attr_name)[0]
|
|
1114
1150
|
|
|
1115
1151
|
|