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.
- chromatic/__init__.py +2 -109
- 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 +58 -41
- chromatic/color/core.py +1008 -1091
- chromatic/color/core.pyi +261 -147
- chromatic/color/iterators.py +3 -3
- chromatic/color/palette.py +489 -481
- chromatic/color/palette.pyi +189 -194
- chromatic/data/__init__.py +22 -187
- chromatic/data/__init__.pyi +16 -15
- chromatic/data/_fetchers.py +55 -0
- chromatic/data/registry.json +5 -0
- chromatic/data/userfont.py +138 -0
- chromatic/data/userfont.pyi +30 -0
- chromatic/data/userfont.schema.json +28 -0
- chromatic/demo.py +153 -137
- chromatic/image/__init__.pyi +70 -0
- chromatic/{ascii → image}/_array.py +379 -368
- chromatic/{ascii → image}/_glyph_proc.py +2 -10
- {chromatic_python-0.2.3.dist-info → chromatic_python-0.3.1.dist-info}/METADATA +2 -1
- chromatic_python-0.3.1.dist-info/RECORD +35 -0
- chromatic/data/images/hotdog.jpg +0 -0
- chromatic_python-0.2.3.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/{ascii → image}/_curses.py +0 -0
- {chromatic_python-0.2.3.dist-info → chromatic_python-0.3.1.dist-info}/WHEEL +0 -0
- {chromatic_python-0.2.3.dist-info → chromatic_python-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
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
|
-
|
|
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
|
|
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[
|
|
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,
|
|
72
|
-
RGBPixel: TypeAlias = ShapedNDArray[tuple[
|
|
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 =
|
|
76
|
-
ColorDictKeys =
|
|
77
|
-
Ansi4BitAlias =
|
|
78
|
-
Ansi8BitAlias =
|
|
79
|
-
Ansi24BitAlias =
|
|
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
|
|
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 = [
|
|
87
|
-
if
|
|
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()
|
|
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
|
|
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(
|
|
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(
|
|
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)] =
|
|
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
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,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
|
|
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
|
|
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
|
|
63
|
+
if 0 <= int(value) <= 0xFFFFFF:
|
|
50
64
|
return True
|
|
51
65
|
elif not strict:
|
|
52
66
|
return False
|
|
53
|
-
raise
|
|
67
|
+
raise ValueError(f"{value!r} is not u24")
|
|
54
68
|
|
|
55
69
|
|
|
56
70
|
def hexstr2rgb(__str: str) -> Int3Tuple:
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
63
|
-
return f"{r:02x}{g:02x}{b:02x}"
|
|
83
|
+
return "%02x%02x%02x" % tuple(rgb)
|
|
64
84
|
|
|
65
85
|
|
|
66
|
-
def
|
|
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
|
|
72
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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 =
|
|
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])
|
|
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
|
-
|
|
280
|
-
|
|
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
|
|
302
|
+
return table
|
|
286
303
|
|
|
287
304
|
|
|
288
305
|
ANSI_4BIT_RGB_MAP = _4b_lookup()
|