chromatic-python 0.2.2__py3-none-any.whl → 0.3.0__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 +2 -103
- chromatic/__init__.pyi +74 -0
- chromatic/_typing.py +38 -20
- chromatic/_version.py +2 -2
- chromatic/color/__init__.py +2 -5
- chromatic/color/__init__.pyi +86 -0
- chromatic/color/colorconv.py +59 -42
- chromatic/color/core.py +987 -1066
- chromatic/color/core.pyi +230 -134
- chromatic/color/iterators.py +3 -3
- chromatic/color/palette.py +465 -458
- chromatic/color/palette.pyi +173 -191
- chromatic/data/__init__.py +15 -177
- chromatic/data/__init__.pyi +9 -15
- chromatic/data/_fetchers.py +55 -0
- chromatic/data/registry.json +5 -0
- chromatic/data/userfont.py +132 -0
- chromatic/data/userfont.pyi +28 -0
- chromatic/data/userfont.schema.json +28 -0
- chromatic/demo.py +153 -132
- chromatic/image/__init__.pyi +70 -0
- chromatic/{ascii → image}/_array.py +123 -55
- chromatic/{ascii → image}/_curses.py +8 -4
- chromatic/{ascii → image}/_glyph_proc.py +9 -3
- {chromatic_python-0.2.2.dist-info → chromatic_python-0.3.0.dist-info}/METADATA +2 -2
- chromatic_python-0.3.0.dist-info/RECORD +35 -0
- {chromatic_python-0.2.2.dist-info → chromatic_python-0.3.0.dist-info}/WHEEL +1 -1
- chromatic/data/images/hotdog.jpg +0 -0
- chromatic_python-0.2.2.dist-info/RECORD +0 -28
- /chromatic/data/{images/butterfly.jpg → butterfly.jpg} +0 -0
- /chromatic/data/{images/escher.png → escher.png} +0 -0
- /chromatic/data/fonts/{IBM_VGA_437_8x16.ttf → vga437.ttf} +0 -0
- /chromatic/data/{images/goblin_virus.png → goblin_virus.png} +0 -0
- /chromatic/{ascii → image}/__init__.py +0 -0
- {chromatic_python-0.2.2.dist-info → chromatic_python-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {chromatic_python-0.2.2.dist-info → chromatic_python-0.3.0.dist-info}/top_level.txt +0 -0
chromatic/__init__.py
CHANGED
|
@@ -3,107 +3,6 @@ try:
|
|
|
3
3
|
except ImportError:
|
|
4
4
|
__version__ = "0.0.0"
|
|
5
5
|
|
|
6
|
-
|
|
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] = {__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
|
|
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
|
-
|
|
44
|
+
if TYPE_CHECKING:
|
|
45
|
+
from .data import UserFont
|
|
44
46
|
|
|
45
47
|
_P = ParamSpec('_P')
|
|
46
48
|
_T = TypeVar('_T')
|
|
@@ -49,10 +51,10 @@ _T_contra = TypeVar('_T_contra', contravariant=True)
|
|
|
49
51
|
_AnyNumber_co = TypeVar('_AnyNumber_co', number, Number, covariant=True)
|
|
50
52
|
|
|
51
53
|
type ArrayReducerFunc[_SCT: generic] = Callable[Concatenate[_ArrayLike[_SCT], _P], NDArray[_SCT]]
|
|
52
|
-
type ShapedNDArray[_Shape: tuple[int, ...], _SCT
|
|
54
|
+
type ShapedNDArray[_Shape: tuple[int, ...], _SCT = generic] = ndarray[_Shape, dtype[_SCT]]
|
|
53
55
|
type MatrixLike[_SCT: generic] = ShapedNDArray[TupleOf2[int], _SCT]
|
|
54
56
|
type SquareMatrix[_I: int, _SCT: generic] = ShapedNDArray[TupleOf2[_I], _SCT]
|
|
55
|
-
type GlyphArray[_SCT: generic] = SquareMatrix[
|
|
57
|
+
type GlyphArray[_SCT: generic] = SquareMatrix[L[24], _SCT]
|
|
56
58
|
type TupleOf2[_T] = tuple[_T, _T]
|
|
57
59
|
type TupleOf3[_T] = tuple[_T, _T, _T]
|
|
58
60
|
|
|
@@ -64,25 +66,41 @@ GlyphBitmask: TypeAlias = GlyphArray[bool]
|
|
|
64
66
|
Bitmask: TypeAlias = MatrixLike[bool]
|
|
65
67
|
GreyscaleGlyphArray: TypeAlias = GlyphArray[float64]
|
|
66
68
|
GreyscaleArray: TypeAlias = MatrixLike[float64]
|
|
67
|
-
RGBArray: TypeAlias = ShapedNDArray[tuple[int, int,
|
|
68
|
-
RGBPixel: TypeAlias = ShapedNDArray[tuple[
|
|
69
|
+
RGBArray: TypeAlias = ShapedNDArray[tuple[int, int, L[3]], uint8]
|
|
70
|
+
RGBPixel: TypeAlias = ShapedNDArray[tuple[L[3]], uint8]
|
|
69
71
|
|
|
70
72
|
RGBImageLike: TypeAlias = Image | RGBArray
|
|
71
|
-
RGBVectorLike: TypeAlias =
|
|
72
|
-
ColorDictKeys =
|
|
73
|
-
Ansi4BitAlias =
|
|
74
|
-
Ansi8BitAlias =
|
|
75
|
-
Ansi24BitAlias =
|
|
73
|
+
RGBVectorLike: TypeAlias = IntSequence | RGBPixel
|
|
74
|
+
ColorDictKeys = L['fg', 'bg']
|
|
75
|
+
Ansi4BitAlias = L['4b']
|
|
76
|
+
Ansi8BitAlias = L['8b']
|
|
77
|
+
Ansi24BitAlias = L['24b']
|
|
76
78
|
AnsiColorAlias = Ansi4BitAlias | Ansi8BitAlias | Ansi24BitAlias
|
|
77
|
-
FontArgType: TypeAlias = FreeTypeFont | UserFont | str
|
|
79
|
+
FontArgType: TypeAlias = 'FreeTypeFont | UserFont | str'
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def eval_annotation(annotation: str, **kwargs) -> Any:
|
|
83
|
+
globals_ = kwargs.get('globals', {}) | globals()
|
|
84
|
+
locals_ = kwargs.get('locals', {})
|
|
85
|
+
try:
|
|
86
|
+
return subtype(eval(annotation, globals_.copy(), locals_.copy()))
|
|
87
|
+
except NameError as e:
|
|
88
|
+
try:
|
|
89
|
+
import typing
|
|
90
|
+
|
|
91
|
+
globals_[e.name] = getattr(typing, e.name)
|
|
92
|
+
return eval_annotation(annotation, globals=globals_, locals=locals_)
|
|
93
|
+
except AttributeError:
|
|
94
|
+
pass
|
|
95
|
+
raise
|
|
78
96
|
|
|
79
97
|
|
|
80
98
|
def type_error_msg(err_obj, *expected, context: str = '', obj_repr=False):
|
|
81
99
|
n_expected = len(expected)
|
|
82
|
-
name_slots = [
|
|
83
|
-
if
|
|
100
|
+
name_slots = ["{%d.__name__!r}" % n for n in range(n_expected)]
|
|
101
|
+
if n_expected > 1:
|
|
84
102
|
name_slots[-1] = f"or {name_slots[-1]}"
|
|
85
|
-
names = (', ' if n_expected > 2 else ' ').join([context.strip()
|
|
103
|
+
names = (', ' if n_expected > 2 else ' ').join([context.strip(), *name_slots]).format(*expected)
|
|
86
104
|
if not obj_repr:
|
|
87
105
|
if not isinstance(err_obj, type):
|
|
88
106
|
err_obj = type(err_obj)
|
|
@@ -100,7 +118,7 @@ def is_matching_type(value, typ):
|
|
|
100
118
|
origin, args = deconstruct_type(typ)
|
|
101
119
|
if origin is Union:
|
|
102
120
|
return any(is_matching_type(value, arg) for arg in args)
|
|
103
|
-
elif origin is
|
|
121
|
+
elif origin is L:
|
|
104
122
|
return value in args
|
|
105
123
|
elif isinstance(typ, TypeVar):
|
|
106
124
|
if typ.__constraints__:
|
|
@@ -188,20 +206,20 @@ def unionize(__iterable: Iterable[SupportsUnion[_T_contra, _T_co]]) -> _T_co:
|
|
|
188
206
|
|
|
189
207
|
_GenericAlias = type(Type[...]) | types.GenericAlias
|
|
190
208
|
_UnionGenericType = type(Union[..., None])
|
|
191
|
-
_LiteralGenericType = type(
|
|
209
|
+
_LiteralGenericType = type(L[''])
|
|
192
210
|
_CallableGenericType = type(Callable[[], ...]) | type(ABC_Callable[[], ...])
|
|
193
211
|
_CallableType = type(Callable) | ABC_Callable
|
|
194
212
|
|
|
195
213
|
|
|
196
214
|
class _BoundedDict[_KT, _VT](OrderedDict[_KT, _VT]):
|
|
215
|
+
"""Bounded OrderedDict, mimics FIFO behavior of `functools.lru_cache`"""
|
|
197
216
|
|
|
198
217
|
def __init__(self, *, maxsize: Optional[int] = 128):
|
|
199
|
-
"""Bounded OrderedDict, mimics FIFO behavior of `functools.lru_cache`"""
|
|
200
218
|
super().__init__()
|
|
201
219
|
if maxsize is not None:
|
|
202
220
|
maxsize = max(maxsize, 0)
|
|
203
221
|
|
|
204
|
-
@wraps(
|
|
222
|
+
@wraps(_BoundedDict.__setitem__)
|
|
205
223
|
def _fifo(_, key, value):
|
|
206
224
|
if maxsize <= len(self):
|
|
207
225
|
self.popitem(last=True)
|
|
@@ -345,7 +363,7 @@ def subtype[_T](typ: _T) -> _T:
|
|
|
345
363
|
return getattr(value, '__args__', ())
|
|
346
364
|
|
|
347
365
|
start = args[literals.pop(0)]
|
|
348
|
-
args_list[args_list.index(start)] =
|
|
366
|
+
args_list[args_list.index(start)] = L[
|
|
349
367
|
*start.__args__,
|
|
350
368
|
*dict.fromkeys(arg for idx in literals for arg in _next_args(idx)),
|
|
351
369
|
]
|
chromatic/_version.py
CHANGED
chromatic/color/__init__.py
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
from .colorconv import *
|
|
3
|
-
from .core import *
|
|
4
|
-
from .palette import *
|
|
1
|
+
import lazy_loader as _lazy
|
|
5
2
|
|
|
6
|
-
__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
|
chromatic/color/colorconv.py
CHANGED
|
@@ -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,69 @@ __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
32
|
from .._typing import Float3Tuple, FloatSequence, Int3Tuple, RGBPixel, RGBVectorLike, ShapedNDArray
|
|
32
33
|
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
@lru_cache
|
|
36
|
+
def _supports_int(typ: type) -> TypeGuard[type[SupportsInt]]:
|
|
37
|
+
return issubclass(typ, SupportsInt)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def is_u24(value, *, strict: bool = False):
|
|
41
|
+
"""Check if value is an unsigned 24-bit integer.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
---------
|
|
45
|
+
value
|
|
46
|
+
Input number
|
|
47
|
+
strict : bool
|
|
48
|
+
Whether to return False or raise ValueError on failure
|
|
49
|
+
|
|
50
|
+
Raises
|
|
51
|
+
------
|
|
52
|
+
ValueError
|
|
53
|
+
Raised when `strict=True` and value is not u24
|
|
54
|
+
"""
|
|
55
|
+
if _supports_int(type(value)):
|
|
56
|
+
if 0 <= int(value) <= 0xFFFFFF:
|
|
37
57
|
return True
|
|
38
58
|
elif not strict:
|
|
39
59
|
return False
|
|
40
|
-
raise
|
|
60
|
+
raise ValueError(f"{value!r} is not u24")
|
|
41
61
|
|
|
42
62
|
|
|
43
63
|
def hexstr2rgb(__str: str) -> Int3Tuple:
|
|
44
|
-
|
|
45
|
-
|
|
64
|
+
n = len(__str)
|
|
65
|
+
if n % 4 == 0: # trunc alpha
|
|
66
|
+
n *= 3
|
|
67
|
+
n //= 4
|
|
68
|
+
__str = __str[:n]
|
|
69
|
+
if n == 3: # rgb -> rrggbb
|
|
70
|
+
__str = ''.join(c * 2 for c in __str)
|
|
71
|
+
if is_u24(value := int(__str, 16), strict=True):
|
|
72
|
+
return int2rgb(value)
|
|
46
73
|
|
|
47
74
|
|
|
48
75
|
def rgb2hexstr(rgb: RGBVectorLike) -> str:
|
|
49
|
-
|
|
50
|
-
return f'{r:02x}{g:02x}{b:02x}'
|
|
76
|
+
return "%02x%02x%02x" % tuple(rgb)
|
|
51
77
|
|
|
52
78
|
|
|
53
|
-
def
|
|
79
|
+
def rgb2int(rgb: RGBVectorLike) -> int:
|
|
54
80
|
r, g, b = map(int, rgb)
|
|
55
81
|
return r << 16 | g << 8 | b
|
|
56
82
|
|
|
57
83
|
|
|
58
|
-
def
|
|
59
|
-
|
|
84
|
+
def int2rgb(__x: int) -> Int3Tuple:
|
|
85
|
+
try:
|
|
86
|
+
return getattr(__x, 'rgb')
|
|
87
|
+
except AttributeError:
|
|
88
|
+
return (__x >> 16) & 0xFF, (__x >> 8) & 0xFF, __x & 0xFF
|
|
60
89
|
|
|
61
90
|
|
|
62
91
|
def xyz2lab(xyz: FloatSequence) -> Float3Tuple:
|
|
@@ -78,7 +107,7 @@ def lab2xyz(lab: FloatSequence) -> Float3Tuple:
|
|
|
78
107
|
x, y, z = map(
|
|
79
108
|
mul,
|
|
80
109
|
(95.047, 100.0, 108.883),
|
|
81
|
-
map(lambda
|
|
110
|
+
map(lambda i: (lambda j: j if j > 0.008856 else (i - 16 / 116) / 7.787)(i**3), (x, y, z)),
|
|
82
111
|
)
|
|
83
112
|
return x, y, z
|
|
84
113
|
|
|
@@ -104,41 +133,30 @@ def xyz2rgb(xyz: ShapedNDArray[tuple[Literal[3]], np.float64]) -> Int3Tuple:
|
|
|
104
133
|
|
|
105
134
|
def hsl2rgb(hsl: FloatSequence) -> Int3Tuple:
|
|
106
135
|
h, s, L = hsl
|
|
107
|
-
h
|
|
136
|
+
h /= 360
|
|
137
|
+
h %= 1
|
|
108
138
|
if h < 0:
|
|
109
139
|
h += 1
|
|
110
|
-
r = g = b = L
|
|
111
140
|
v = (L * (1.0 + s)) if L <= 0.5 else (L + s - L * s)
|
|
112
|
-
if v > 0:
|
|
141
|
+
if v > 0 and 0 <= (sextant := int(h)) <= 5:
|
|
113
142
|
m = L + L - v
|
|
114
143
|
sv = (v - m) / v
|
|
115
144
|
h *= 6.0
|
|
116
|
-
|
|
117
|
-
fract = h - sextant
|
|
118
|
-
vsf = v * sv * fract
|
|
145
|
+
vsf = v * sv * (h - sextant)
|
|
119
146
|
mid1 = m + vsf
|
|
120
147
|
mid2 = v - vsf
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
elif sextant == 2:
|
|
126
|
-
r, g, b = m, v, mid1
|
|
127
|
-
elif sextant == 3:
|
|
128
|
-
r, g, b = m, mid2, v
|
|
129
|
-
elif sextant == 4:
|
|
130
|
-
r, g, b = mid1, m, v
|
|
131
|
-
elif sextant == 5:
|
|
132
|
-
r, g, b = v, m, mid2
|
|
133
|
-
r, g, b = (round(x * 255) for x in (r, g, b))
|
|
148
|
+
r, g, b = (
|
|
149
|
+
round(x * 0xFF)
|
|
150
|
+
for x in [[v, mid1, m], [mid2, v, m], [m, v, mid1], [m, mid2, v], [mid1, m, v]][sextant]
|
|
151
|
+
)
|
|
134
152
|
else:
|
|
135
|
-
r, g, b =
|
|
153
|
+
r, g, b = [round(L * 0xFF)] * 3
|
|
136
154
|
return r, g, b
|
|
137
155
|
|
|
138
156
|
|
|
139
157
|
def rgb2hsl(rgb: RGBVectorLike) -> Float3Tuple:
|
|
140
158
|
r, g, b = (x / 255.0 for x in rgb)
|
|
141
|
-
m, v = sorted([r, g, b])
|
|
159
|
+
m, _, v = sorted([r, g, b])
|
|
142
160
|
L = (m + v) / 2
|
|
143
161
|
h = s = 0
|
|
144
162
|
if L > 0:
|
|
@@ -245,7 +263,7 @@ def ansi_4bit_to_rgb(value: int):
|
|
|
245
263
|
return ANSI_4BIT_RGB[value]
|
|
246
264
|
|
|
247
265
|
|
|
248
|
-
def _4b_lookup():
|
|
266
|
+
def _4b_lookup() -> dict[Int3Tuple, Int3Tuple]:
|
|
249
267
|
def rgb_dist(rgb, ansi):
|
|
250
268
|
r_mean = (rgb[:, 0:1] + ansi[:, 0]) / 2
|
|
251
269
|
r_diff = (rgb[:, 0:1] - ansi[:, 0]) * (2 + r_mean / 256)
|
|
@@ -257,12 +275,11 @@ def _4b_lookup():
|
|
|
257
275
|
quants = np.stack(
|
|
258
276
|
np.meshgrid(*np.repeat(np.arange(32).reshape([1, -1]), 3, 0), indexing='ij'), axis=-1
|
|
259
277
|
).reshape([-1, 3])
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
table = {
|
|
278
|
+
nearest_colors = rgb_4b_arr[np.argmin(rgb_dist(quants * 8, rgb_4b_arr), axis=1)]
|
|
279
|
+
table: dict = {
|
|
263
280
|
tuple(map(int, color)): tuple(map(int, nearest_colors[i])) for i, color in enumerate(quants)
|
|
264
281
|
}
|
|
265
|
-
return
|
|
282
|
+
return table
|
|
266
283
|
|
|
267
284
|
|
|
268
285
|
ANSI_4BIT_RGB_MAP = _4b_lookup()
|