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 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 = [f"{{{n}.__qualname__!r}}" for n in range(n_expected)]
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(bitwise_or, __iterable)
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 = UniqueAttrs._make, attrgetter(*attr_names) # noqa
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 setuptools_scm
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, Union
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.0'
16
- __version_tuple__ = version_tuple = (0, 2, 0)
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, no_reset=True)
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 = (255, 255, 255),
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._sgr_.is_reset():
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._sgr_
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._sgr_ if p.is_color()]
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
- AnyStr: (str, bytes)
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: (v if v not in frozenset(x for c in ' \t\n\r\v\f' for x in (c, ord(c))) else ' ')
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