chromatic-python 0.2.3__py3-none-any.whl → 0.3.1__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.
Files changed (36) hide show
  1. chromatic/__init__.py +2 -109
  2. chromatic/__init__.pyi +74 -0
  3. chromatic/_typing.py +38 -20
  4. chromatic/_version.py +2 -2
  5. chromatic/color/__init__.py +2 -5
  6. chromatic/color/__init__.pyi +86 -0
  7. chromatic/color/colorconv.py +58 -41
  8. chromatic/color/core.py +1008 -1091
  9. chromatic/color/core.pyi +261 -147
  10. chromatic/color/iterators.py +3 -3
  11. chromatic/color/palette.py +489 -481
  12. chromatic/color/palette.pyi +189 -194
  13. chromatic/data/__init__.py +22 -187
  14. chromatic/data/__init__.pyi +16 -15
  15. chromatic/data/_fetchers.py +55 -0
  16. chromatic/data/registry.json +5 -0
  17. chromatic/data/userfont.py +138 -0
  18. chromatic/data/userfont.pyi +30 -0
  19. chromatic/data/userfont.schema.json +28 -0
  20. chromatic/demo.py +153 -137
  21. chromatic/image/__init__.pyi +70 -0
  22. chromatic/{ascii → image}/_array.py +379 -368
  23. chromatic/{ascii → image}/_glyph_proc.py +2 -10
  24. {chromatic_python-0.2.3.dist-info → chromatic_python-0.3.1.dist-info}/METADATA +2 -1
  25. chromatic_python-0.3.1.dist-info/RECORD +35 -0
  26. chromatic/data/images/hotdog.jpg +0 -0
  27. chromatic_python-0.2.3.dist-info/RECORD +0 -28
  28. /chromatic/data/{images/butterfly.jpg → butterfly.jpg} +0 -0
  29. /chromatic/data/{images/escher.png → escher.png} +0 -0
  30. /chromatic/data/fonts/{IBM_VGA_437_8x16.ttf → vga437.ttf} +0 -0
  31. /chromatic/data/{images/goblin_virus.png → goblin_virus.png} +0 -0
  32. /chromatic/{ascii → image}/__init__.py +0 -0
  33. /chromatic/{ascii → image}/_curses.py +0 -0
  34. {chromatic_python-0.2.3.dist-info → chromatic_python-0.3.1.dist-info}/WHEEL +0 -0
  35. {chromatic_python-0.2.3.dist-info → chromatic_python-0.3.1.dist-info}/licenses/LICENSE +0 -0
  36. {chromatic_python-0.2.3.dist-info → chromatic_python-0.3.1.dist-info}/top_level.txt +0 -0
chromatic/__init__.py CHANGED
@@ -3,113 +3,6 @@ try:
3
3
  except ImportError:
4
4
  __version__ = "0.0.0"
5
5
 
6
- from . import ascii, color, data
7
- from .ascii import (
8
- AnsiImage,
9
- ansi2img,
10
- ansify,
11
- ansi_quantize,
12
- ascii2img,
13
- ascii_printable,
14
- contrast_stretch,
15
- cp437_printable,
16
- equalize_white_point,
17
- get_font_key,
18
- get_font_object,
19
- img2ansi,
20
- img2ascii,
21
- read_ans,
22
- render_ans,
23
- reshape_ansi,
24
- to_sgr_array,
25
- )
26
- from .ascii._glyph_proc import get_glyph_masks
27
- from .color import (
28
- Back,
29
- Color,
30
- ColorNamespace,
31
- ColorStr,
32
- Fore,
33
- SgrParameter,
34
- Style,
35
- ansicolor24Bit,
36
- ansicolor4Bit,
37
- ansicolor8Bit,
38
- colorbytes,
39
- named_color,
40
- )
41
- from .data import register_user_font
6
+ import lazy_loader as _lazy
42
7
 
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] = {
56
- __name__: {'__module__': sys.modules[__name__]}
57
- }
58
- children = set()
59
- for pkg in find_packages(path):
60
- children.add(pkg)
61
- pkg_path = path + '/' + pkg.replace('.', '/')
62
- if sys.version_info.major == 2 or (
63
- sys.version_info.major == 3 and sys.version_info.minor < 6
64
- ):
65
- for _, name, ispkg in iter_modules([pkg_path]):
66
- if not ispkg:
67
- children.add(f"{pkg}.{name}")
68
- else:
69
- for info in iter_modules([pkg_path]):
70
- if not info.ispkg:
71
- children.add(f"{pkg}.{info.name}")
72
- for child in children:
73
- name = f"{__name__}.{child}"
74
- depth = tree
75
- for node in name.split('.'):
76
- if node not in depth:
77
- depth[node] = {}
78
- depth = depth[node]
79
- if name in sys.modules:
80
- depth['__module__'] = sys.modules[name]
81
- return tree
82
-
83
- def publicize_modules(modulename: str, tree: dict[str, dict | ModuleType]):
84
- def is_local(obj: object):
85
- if isinstance(obj, ModuleType):
86
- return obj.__spec__.parent == modulename
87
- elif hasattr(obj, '__module__'):
88
- return obj.__module__ == modulename
89
- return obj is not None
90
-
91
- for name, subtree in tree.items():
92
- if name == '__module__' and isinstance(subtree, ModuleType):
93
- submodule = subtree
94
- init_dir = dir(submodule)
95
-
96
- @wraps(submodule.__dir__)
97
- def wrapped():
98
- s = set(
99
- attr
100
- for attr in init_dir
101
- if is_local(getattr(submodule, attr, None))
102
- )
103
- if hasattr(submodule, '__all__'):
104
- s.update(submodule.__all__)
105
- return list(s)
106
-
107
- sys.modules[submodule.__name__].__dir__ = wrapped
108
-
109
- else:
110
- publicize_modules(f"{modulename}.{name}", subtree)
111
-
112
- publicize_modules(*find_modules(os.path.split(__file__)[0]).popitem())
113
-
114
- finally:
115
- del find_modules, publicize_modules
8
+ __getattr__, __dir__, __all__ = _lazy.attach_stub(__name__, __file__)
chromatic/__init__.pyi ADDED
@@ -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
chromatic/_typing.py CHANGED
@@ -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
- from chromatic.data import UserFont
44
+ if TYPE_CHECKING:
45
+ from .data import UserFont
44
46
 
45
47
  _P = ParamSpec('_P')
46
48
  _T = TypeVar('_T')
@@ -51,12 +53,12 @@ _AnyNumber_co = TypeVar('_AnyNumber_co', number, Number, covariant=True)
51
53
  type ArrayReducerFunc[_SCT: generic] = Callable[
52
54
  Concatenate[_ArrayLike[_SCT], _P], NDArray[_SCT]
53
55
  ]
54
- type ShapedNDArray[_Shape: tuple[int, ...], _SCT: generic] = ndarray[
56
+ type ShapedNDArray[_Shape: tuple[int, ...], _SCT = generic] = ndarray[
55
57
  _Shape, dtype[_SCT]
56
58
  ]
57
59
  type MatrixLike[_SCT: generic] = ShapedNDArray[TupleOf2[int], _SCT]
58
60
  type SquareMatrix[_I: int, _SCT: generic] = ShapedNDArray[TupleOf2[_I], _SCT]
59
- type GlyphArray[_SCT: generic] = SquareMatrix[Literal[24], _SCT]
61
+ type GlyphArray[_SCT: generic] = SquareMatrix[L[24], _SCT]
60
62
  type TupleOf2[_T] = tuple[_T, _T]
61
63
  type TupleOf3[_T] = tuple[_T, _T, _T]
62
64
 
@@ -68,27 +70,43 @@ GlyphBitmask: TypeAlias = GlyphArray[bool]
68
70
  Bitmask: TypeAlias = MatrixLike[bool]
69
71
  GreyscaleGlyphArray: TypeAlias = GlyphArray[float64]
70
72
  GreyscaleArray: TypeAlias = MatrixLike[float64]
71
- RGBArray: TypeAlias = ShapedNDArray[tuple[int, int, Literal[3]], uint8]
72
- RGBPixel: TypeAlias = ShapedNDArray[tuple[Literal[3]], uint8]
73
+ RGBArray: TypeAlias = ShapedNDArray[tuple[int, int, L[3]], uint8]
74
+ RGBPixel: TypeAlias = ShapedNDArray[tuple[L[3]], uint8]
73
75
 
74
76
  RGBImageLike: TypeAlias = Image | RGBArray
75
- RGBVectorLike: TypeAlias = Int3Tuple | IntSequence | RGBPixel
76
- ColorDictKeys = Literal['fg', 'bg']
77
- Ansi4BitAlias = Literal['4b']
78
- Ansi8BitAlias = Literal['8b']
79
- Ansi24BitAlias = Literal['24b']
77
+ RGBVectorLike: TypeAlias = IntSequence | RGBPixel
78
+ ColorDictKeys = L['fg', 'bg']
79
+ Ansi4BitAlias = L['4b']
80
+ Ansi8BitAlias = L['8b']
81
+ Ansi24BitAlias = L['24b']
80
82
  AnsiColorAlias = Ansi4BitAlias | Ansi8BitAlias | Ansi24BitAlias
81
- FontArgType: TypeAlias = FreeTypeFont | UserFont | str | int
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
82
100
 
83
101
 
84
102
  def type_error_msg(err_obj, *expected, context: str = '', obj_repr=False):
85
103
  n_expected = len(expected)
86
- name_slots = ['{%d.__qualname__!r}' % n for n in range(n_expected)]
87
- if name_slots and n_expected > 1:
104
+ name_slots = ["{%d.__name__!r}" % n for n in range(n_expected)]
105
+ if n_expected > 1:
88
106
  name_slots[-1] = f"or {name_slots[-1]}"
89
107
  names = (
90
108
  (', ' if n_expected > 2 else ' ')
91
- .join([context.strip()] + name_slots)
109
+ .join([context.strip(), *name_slots])
92
110
  .format(*expected)
93
111
  )
94
112
  if not obj_repr:
@@ -108,7 +126,7 @@ def is_matching_type(value, typ):
108
126
  origin, args = deconstruct_type(typ)
109
127
  if origin is Union:
110
128
  return any(is_matching_type(value, arg) for arg in args)
111
- elif origin is Literal:
129
+ elif origin is L:
112
130
  return value in args
113
131
  elif isinstance(typ, TypeVar):
114
132
  if typ.__constraints__:
@@ -201,20 +219,20 @@ def unionize(__iterable: Iterable[SupportsUnion[_T_contra, _T_co]]) -> _T_co:
201
219
 
202
220
  _GenericAlias = type(Type[...]) | types.GenericAlias
203
221
  _UnionGenericType = type(Union[..., None])
204
- _LiteralGenericType = type(Literal[''])
222
+ _LiteralGenericType = type(L[''])
205
223
  _CallableGenericType = type(Callable[[], ...]) | type(ABC_Callable[[], ...])
206
224
  _CallableType = type(Callable) | ABC_Callable
207
225
 
208
226
 
209
227
  class _BoundedDict[_KT, _VT](OrderedDict[_KT, _VT]):
228
+ """Bounded OrderedDict, mimics FIFO behavior of `functools.lru_cache`"""
210
229
 
211
230
  def __init__(self, *, maxsize: Optional[int] = 128):
212
- """Bounded OrderedDict, mimics FIFO behavior of `functools.lru_cache`"""
213
231
  super().__init__()
214
232
  if maxsize is not None:
215
233
  maxsize = max(maxsize, 0)
216
234
 
217
- @wraps(self.__setitem__)
235
+ @wraps(_BoundedDict.__setitem__)
218
236
  def _fifo(_, key, value):
219
237
  if maxsize <= len(self):
220
238
  self.popitem(last=True)
@@ -368,7 +386,7 @@ def subtype[_T](typ: _T) -> _T:
368
386
  return getattr(value, '__args__', ())
369
387
 
370
388
  start = args[literals.pop(0)]
371
- args_list[args_list.index(start)] = Literal[
389
+ args_list[args_list.index(start)] = L[
372
390
  *start.__args__,
373
391
  *dict.fromkeys(arg for idx in literals for arg in _next_args(idx)),
374
392
  ]
chromatic/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.2.3'
21
- __version_tuple__ = version_tuple = (0, 2, 3)
20
+ __version__ = version = '0.3.1'
21
+ __version_tuple__ = version_tuple = (0, 3, 1)
@@ -1,6 +1,3 @@
1
- from . import core, palette
2
- from .colorconv import *
3
- from .core import *
4
- from .palette import *
1
+ import lazy_loader as _lazy
5
2
 
6
- __all__ = list(set(core.__all__) | set(colorconv.__all__) | set(palette.__all__))
3
+ __getattr__, __dir__, __all__ = _lazy.attach_stub(__name__, __file__)
@@ -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
- 'hex2rgb',
5
+ 'int2rgb',
6
6
  'hexstr2rgb',
7
7
  'hsl2rgb',
8
8
  'hsv2rgb',
9
- 'is_hex_rgb',
9
+ 'is_u24',
10
10
  'lab2rgb',
11
11
  'lab2xyz',
12
12
  'nearest_ansi_4bit_rgb',
13
13
  'nearest_ansi_8bit_rgb',
14
- 'rgb2hex',
14
+ 'rgb2int',
15
15
  'rgb2hexstr',
16
16
  'rgb2hsl',
17
17
  'rgb2hsv',
@@ -23,9 +23,9 @@ __all__ = [
23
23
  'xyz2rgb',
24
24
  ]
25
25
 
26
- from operator import mul, truediv
27
26
  from functools import lru_cache
28
- from typing import Final, Literal, SupportsInt, cast, TypeGuard
27
+ from operator import mul, truediv
28
+ from typing import Final, Literal, SupportsInt, TypeGuard
29
29
 
30
30
  import numpy as np
31
31
 
@@ -44,32 +44,55 @@ def _supports_int(typ: type) -> TypeGuard[type[SupportsInt]]:
44
44
  return issubclass(typ, SupportsInt)
45
45
 
46
46
 
47
- def is_hex_rgb(value, *, strict: bool = False):
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
+ """
48
62
  if _supports_int(type(value)):
49
- if 0x0 <= int(value) <= 0xFFFFFF:
63
+ if 0 <= int(value) <= 0xFFFFFF:
50
64
  return True
51
65
  elif not strict:
52
66
  return False
53
- raise TypeError(f"{value!r} is not a valid RGB color") from None
67
+ raise ValueError(f"{value!r} is not u24")
54
68
 
55
69
 
56
70
  def hexstr2rgb(__str: str) -> Int3Tuple:
57
- if is_hex_rgb(value := int(__str, 16), strict=True):
58
- return hex2rgb(value)
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)
59
80
 
60
81
 
61
82
  def rgb2hexstr(rgb: RGBVectorLike) -> str:
62
- r, g, b = rgb
63
- return f"{r:02x}{g:02x}{b:02x}"
83
+ return "%02x%02x%02x" % tuple(rgb)
64
84
 
65
85
 
66
- def rgb2hex(rgb: RGBVectorLike) -> int:
86
+ def rgb2int(rgb: RGBVectorLike) -> int:
67
87
  r, g, b = map(int, rgb)
68
88
  return r << 16 | g << 8 | b
69
89
 
70
90
 
71
- def hex2rgb(value: int) -> Int3Tuple:
72
- return (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF
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
73
96
 
74
97
 
75
98
  def xyz2lab(xyz: FloatSequence) -> Float3Tuple:
@@ -122,41 +145,36 @@ def xyz2rgb(xyz: ShapedNDArray[tuple[Literal[3]], np.float64]) -> Int3Tuple:
122
145
 
123
146
  def hsl2rgb(hsl: FloatSequence) -> Int3Tuple:
124
147
  h, s, L = hsl
125
- h = (h / 360) % 1
148
+ h /= 360
149
+ h %= 1
126
150
  if h < 0:
127
151
  h += 1
128
- r = g = b = L
129
152
  v = (L * (1.0 + s)) if L <= 0.5 else (L + s - L * s)
130
- if v > 0:
153
+ if v > 0 and 0 <= (sextant := int(h)) <= 5:
131
154
  m = L + L - v
132
155
  sv = (v - m) / v
133
156
  h *= 6.0
134
- sextant = int(h)
135
- fract = h - sextant
136
- vsf = v * sv * fract
157
+ vsf = v * sv * (h - sextant)
137
158
  mid1 = m + vsf
138
159
  mid2 = v - vsf
139
- if sextant == 0:
140
- r, g, b = v, mid1, m
141
- elif sextant == 1:
142
- r, g, b = mid2, v, m
143
- elif sextant == 2:
144
- r, g, b = m, v, mid1
145
- elif sextant == 3:
146
- r, g, b = m, mid2, v
147
- elif sextant == 4:
148
- r, g, b = mid1, m, v
149
- elif sextant == 5:
150
- r, g, b = v, m, mid2
151
- 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
+ )
152
170
  else:
153
- r, g, b = (round(L * 255) for _ in range(3))
171
+ r, g, b = [round(L * 0xFF)] * 3
154
172
  return r, g, b
155
173
 
156
174
 
157
175
  def rgb2hsl(rgb: RGBVectorLike) -> Float3Tuple:
158
176
  r, g, b = (x / 255.0 for x in rgb)
159
- m, v = sorted([r, g, b])[::2]
177
+ m, _, v = sorted([r, g, b])
160
178
  L = (m + v) / 2
161
179
  h = s = 0
162
180
  if L > 0:
@@ -263,7 +281,7 @@ def ansi_4bit_to_rgb(value: int):
263
281
  return ANSI_4BIT_RGB[value]
264
282
 
265
283
 
266
- def _4b_lookup():
284
+ def _4b_lookup() -> dict[Int3Tuple, Int3Tuple]:
267
285
  def rgb_dist(rgb, ansi):
268
286
  r_mean = (rgb[:, 0:1] + ansi[:, 0]) / 2
269
287
  r_diff = (rgb[:, 0:1] - ansi[:, 0]) * (2 + r_mean / 256)
@@ -276,13 +294,12 @@ def _4b_lookup():
276
294
  np.meshgrid(*np.repeat(np.arange(32).reshape([1, -1]), 3, 0), indexing='ij'),
277
295
  axis=-1,
278
296
  ).reshape([-1, 3])
279
- rgb_colors = quants * 8
280
- nearest_colors = rgb_4b_arr[np.argmin(rgb_dist(rgb_colors, rgb_4b_arr), axis=1)]
281
- table = {
297
+ nearest_colors = rgb_4b_arr[np.argmin(rgb_dist(quants * 8, rgb_4b_arr), axis=1)]
298
+ table: dict = {
282
299
  tuple(map(int, color)): tuple(map(int, nearest_colors[i]))
283
300
  for i, color in enumerate(quants)
284
301
  }
285
- return cast(dict[Int3Tuple, Int3Tuple], table)
302
+ return table
286
303
 
287
304
 
288
305
  ANSI_4BIT_RGB_MAP = _4b_lookup()