chromatic-python 0.4.1__tar.gz → 0.5.0__tar.gz

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 (50) hide show
  1. chromatic_python-0.5.0/.gitattributes +20 -0
  2. chromatic_python-0.5.0/.gitignore +28 -0
  3. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/PKG-INFO +7 -8
  4. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/_typing.py +17 -20
  5. chromatic_python-0.5.0/chromatic/_version.py +24 -0
  6. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/color/colorconv.py +37 -34
  7. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/color/core.py +495 -337
  8. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/color/core.pyi +159 -163
  9. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/color/iterators.py +2 -2
  10. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/color/palette.py +62 -54
  11. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/color/palette.pyi +8 -50
  12. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/data/__init__.py +3 -1
  13. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/data/__init__.pyi +3 -1
  14. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/data/_fetchers.py +1 -0
  15. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/data/userfont.py +5 -19
  16. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/data/userfont.pyi +3 -1
  17. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/demo.py +10 -10
  18. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/image/_array.py +238 -212
  19. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/image/_curses.py +14 -16
  20. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/image/_glyph.py +16 -14
  21. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic_python.egg-info/PKG-INFO +7 -8
  22. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic_python.egg-info/SOURCES.txt +0 -2
  23. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic_python.egg-info/requires.txt +2 -2
  24. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/pyproject.toml +23 -15
  25. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/tests/test_color_str.py +34 -13
  26. chromatic_python-0.4.1/.gitattributes +0 -1
  27. chromatic_python-0.4.1/.gitignore +0 -6
  28. chromatic_python-0.4.1/LICENSE +0 -21
  29. chromatic_python-0.4.1/chromatic/_version.py +0 -34
  30. chromatic_python-0.4.1/requirements.txt +0 -53
  31. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/.github/workflows/publish.yml +0 -0
  32. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/README.md +0 -0
  33. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/banner.png +0 -0
  34. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/__init__.py +0 -0
  35. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/__init__.pyi +1 -1
  36. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/color/__init__.py +0 -0
  37. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/color/__init__.pyi +3 -3
  38. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/data/butterfly.jpg +0 -0
  39. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/data/escher.png +0 -0
  40. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/data/fonts/consolas.ttf +0 -0
  41. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/data/fonts/vga437.ttf +0 -0
  42. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/data/goblin_virus.png +0 -0
  43. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/data/registry.json +0 -0
  44. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/data/userfont.schema.json +0 -0
  45. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/image/__init__.py +0 -0
  46. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic/image/__init__.pyi +0 -0
  47. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic_python.egg-info/dependency_links.txt +0 -0
  48. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/chromatic_python.egg-info/top_level.txt +0 -0
  49. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/setup.cfg +0 -0
  50. {chromatic_python-0.4.1 → chromatic_python-0.5.0}/tests/__init__.py +0 -0
@@ -0,0 +1,20 @@
1
+ # python sources
2
+ *.py text diff=python
3
+ *.pyi text diff=python
4
+
5
+ # documents
6
+ *.md text diff=markdown
7
+ *.txt text
8
+
9
+ # graphics
10
+ *.png binary
11
+ *.jpg binary
12
+ *.jpeg binary
13
+ *.ttf binary
14
+ *.ttc binary
15
+
16
+ # serialization
17
+ *.json text
18
+ *.toml text
19
+ *.yaml text
20
+ *.yml text
@@ -0,0 +1,28 @@
1
+ # editor temp/backup files
2
+ *.bak
3
+ *.diff
4
+ .idea/
5
+ *.tmp
6
+ .vscode
7
+
8
+ # compiled source
9
+ *.dll
10
+ *.exe
11
+ *.o
12
+ *.o.d
13
+ *.py[ocd]
14
+ *.so
15
+ *.mod
16
+
17
+ # python files
18
+ build
19
+ build-*
20
+ dist
21
+ _version.py
22
+ doc/build
23
+ *.egg-info
24
+ venv/
25
+
26
+ # this project
27
+ userfont.json
28
+ tests/_*
@@ -1,27 +1,26 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chromatic-python
3
- Version: 0.4.1
3
+ Version: 0.5.0
4
4
  Summary: ANSI art image processing and colored terminal text
5
5
  Author: crypt0lith
6
- Project-URL: Homepage, https://github.com/crypt0lith/chromatic
6
+ License-Expression: MIT
7
+ Project-URL: repository, https://github.com/crypt0lith/chromatic.git
7
8
  Keywords: ansi,ascii,art,font,image,terminal,parser
8
- Classifier: Programming Language :: Python :: 3.12
9
+ Classifier: Programming Language :: Python :: 3.13
9
10
  Classifier: Operating System :: OS Independent
10
11
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
11
12
  Classifier: Typing :: Typed
12
- Requires-Python: >=3.12
13
+ Requires-Python: <4.0,>=3.13
13
14
  Description-Content-Type: text/markdown
14
- License-File: LICENSE
15
15
  Requires-Dist: numpy<2.6,>=1.25.2
16
- Requires-Dist: pillow!=11.2.*,<12.0,>=10.1
16
+ Requires-Dist: pillow>=12.2.0
17
17
  Requires-Dist: networkx<4.0,>=3.0
18
18
  Requires-Dist: scipy<2.0,>=1.11.4
19
19
  Requires-Dist: opencv-python
20
20
  Requires-Dist: scikit-image
21
21
  Requires-Dist: scikit-learn
22
- Requires-Dist: fonttools
22
+ Requires-Dist: fonttools>=4.60.2
23
23
  Requires-Dist: lazy_loader
24
- Dynamic: license-file
25
24
 
26
25
  ![image](https://raw.githubusercontent.com/crypt0lith/chromatic/master/banner.png)
27
26
 
@@ -7,8 +7,8 @@ import types
7
7
  from collections import OrderedDict, namedtuple
8
8
  from collections.abc import Callable as ABC_Callable
9
9
  from functools import reduce, wraps
10
- from numbers import Number
11
10
  from typing import (
11
+ TYPE_CHECKING,
12
12
  Any,
13
13
  Callable,
14
14
  Concatenate,
@@ -20,13 +20,12 @@ from typing import (
20
20
  ParamSpec,
21
21
  Protocol,
22
22
  Sequence,
23
- TYPE_CHECKING,
24
23
  Type,
25
24
  TypeAlias,
26
25
  TypeAliasType,
26
+ TypedDict,
27
27
  TypeGuard,
28
28
  TypeVar,
29
- TypedDict,
30
29
  Union,
31
30
  Unpack,
32
31
  cast,
@@ -36,19 +35,17 @@ from typing import (
36
35
  runtime_checkable,
37
36
  )
38
37
 
38
+ from numpy import dtype, float64, generic, ndarray, uint8
39
+ from numpy._typing import NDArray, _ArrayLike
39
40
  from PIL.Image import Image
40
41
  from PIL.ImageFont import FreeTypeFont
41
- from numpy import dtype, float64, generic, ndarray, number, uint8
42
- from numpy._typing import NDArray, _ArrayLike
43
42
 
44
43
  if TYPE_CHECKING:
45
44
  from .data import UserFont
46
45
 
47
46
  _P = ParamSpec('_P')
48
- _T = TypeVar('_T')
49
47
  _T_co = TypeVar('_T_co', covariant=True)
50
48
  _T_contra = TypeVar('_T_contra', contravariant=True)
51
- _AnyNumber_co = TypeVar('_AnyNumber_co', number, Number, covariant=True)
52
49
 
53
50
  type ArrayReducerFunc[_SCT: generic] = Callable[
54
51
  Concatenate[_ArrayLike[_SCT], _P], NDArray[_SCT]
@@ -180,16 +177,16 @@ def is_matching_type(value, typ):
180
177
  return False
181
178
 
182
179
 
183
- def is_matching_typed_dict(__d: dict, typed_dict: type[dict]) -> tuple[bool, str]:
184
- if TypedDict not in getattr(__d, '__orig_bases__', ()):
185
- return False, type_error_msg(__d, dict)
180
+ def is_matching_typed_dict(d: dict, /, typed_dict: type[dict]) -> tuple[bool, str]:
181
+ if TypedDict not in getattr(d, '__orig_bases__', ()):
182
+ return False, type_error_msg(d, dict)
186
183
  expected = get_type_hints(typed_dict)
187
- if unexpected := __d.keys() - expected:
184
+ if unexpected := d.keys() - expected:
188
185
  return False, f"unexpected keyword arguments: {unexpected}"
189
- if missing := set(getattr(typed_dict, '__required_keys__', expected)) - __d.keys():
186
+ if missing := set(getattr(typed_dict, '__required_keys__', expected)) - d.keys():
190
187
  return False, f"missing required keys: {missing}"
191
188
  for name, typ in expected.items():
192
- field = __d.get(name)
189
+ field = d.get(name)
193
190
  if field is None or is_matching_type(field, typ):
194
191
  continue
195
192
  return False, type_error_msg(
@@ -214,15 +211,14 @@ class SupportsUnion(Protocol[_T_contra, _T_co]):
214
211
  def __or__(self, x: _T_contra, /) -> _T_co: ...
215
212
 
216
213
 
217
- def unionize(__iterable: Iterable[SupportsUnion[_T_contra, _T_co]]) -> _T_co:
218
- return reduce(op.or_, __iterable)
214
+ def unionize(iterable: Iterable[SupportsUnion[_T_contra, _T_co]], /) -> _T_co:
215
+ return reduce(op.or_, iterable)
219
216
 
220
217
 
221
218
  _GenericAlias = type(Type[...]) | types.GenericAlias
222
219
  _UnionGenericType = type(Union[..., None])
223
220
  _LiteralGenericType = type(L[''])
224
221
  _CallableGenericType = type(Callable[[], ...]) | type(ABC_Callable[[], ...])
225
- _CallableType = type(Callable) | ABC_Callable
226
222
 
227
223
 
228
224
  class _BoundedDict[_KT, _VT](OrderedDict[_KT, _VT]):
@@ -346,11 +342,12 @@ def _sort_attrs(obj, tp_name, attr_names):
346
342
  return attr_names, field_names
347
343
 
348
344
 
349
- def subtype[_T](typ: _T) -> _T:
350
- type TypeVarDict = dict[TypeVar, ...]
345
+ type TypeVarDict = dict[TypeVar, ...]
351
346
 
352
- def _serialize(__item: tuple[Any, Hashable]):
353
- key, value = __item
347
+
348
+ def subtype[_T](typ: _T) -> _T:
349
+ def _serialize(item: tuple[Any, Hashable], /):
350
+ key, value = item
354
351
  return _unique_attrs(key), hash(value)
355
352
 
356
353
  def _cache_key(tp, tvars: TypeVarDict):
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.5.0'
22
+ __version_tuple__ = version_tuple = (0, 5, 0)
23
+
24
+ __commit_id__ = commit_id = 'g066855df5'
@@ -25,6 +25,7 @@ __all__ = [
25
25
 
26
26
  from functools import lru_cache
27
27
  from operator import mul, truediv
28
+ from types import MappingProxyType as mappingproxy
28
29
  from typing import Final, Literal, SupportsInt, TypeGuard
29
30
 
30
31
  import numpy as np
@@ -40,11 +41,11 @@ from .._typing import (
40
41
 
41
42
 
42
43
  @lru_cache
43
- def _supports_int(typ: type) -> TypeGuard[type[SupportsInt]]:
44
+ def _supports_int(typ: type, /) -> TypeGuard[type[SupportsInt]]:
44
45
  return issubclass(typ, SupportsInt)
45
46
 
46
47
 
47
- def is_u24(value, *, strict: bool = False):
48
+ def is_u24(value, *, strict: bool = False) -> bool:
48
49
  """Check if value is an unsigned 24-bit integer.
49
50
 
50
51
  Parameters
@@ -60,44 +61,46 @@ def is_u24(value, *, strict: bool = False):
60
61
  Raised when `strict=True` and value is not u24
61
62
  """
62
63
  if _supports_int(value.__class__):
63
- if 0 <= int(value) <= 0xFFFFFF:
64
+ if 0 <= int(value) < (1 << 24):
64
65
  return True
65
66
  elif not strict:
66
67
  return False
67
68
  raise ValueError(f"{value!r} is not u24")
68
69
 
69
70
 
70
- def hexstr2rgb(__str: str) -> Int3Tuple:
71
- n = len(__str)
71
+ def hexstr2rgb(s: str, /) -> Int3Tuple:
72
+ n = len(s)
72
73
  if n % 4 == 0: # trunc alpha
73
74
  n *= 3
74
75
  n //= 4
75
- __str = __str[:n]
76
+ s = s[:n]
76
77
  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)
78
+ s = ''.join(c * 2 for c in s)
79
+ x = int(s, 16)
80
+ if not 0 <= x < (1 << 24):
81
+ raise ValueError(f"{x:#x} is not u24")
82
+ return int2rgb(x)
80
83
 
81
84
 
82
- def rgb2hexstr(rgb: RGBVectorLike) -> str:
85
+ def rgb2hexstr(rgb: RGBVectorLike, /) -> str:
83
86
  return "%02x%02x%02x" % tuple(rgb)
84
87
 
85
88
 
86
- def rgb2int(rgb: RGBVectorLike) -> int:
89
+ def rgb2int(rgb: RGBVectorLike, /) -> int:
87
90
  r, g, b = map(int, rgb)
88
91
  return r << 16 | g << 8 | b
89
92
 
90
93
 
91
- def int2rgb(__x: int) -> Int3Tuple:
94
+ def int2rgb(x: int, /) -> Int3Tuple:
92
95
  try:
93
- return getattr(__x, 'rgb')
96
+ return getattr(x, 'rgb')
94
97
  except AttributeError:
95
98
  pass
96
- x = int(__x) & 0xFFFFFF
99
+ x = int(x) & 0xFFFFFF
97
100
  return (x >> 16) & 0xFF, (x >> 8) & 0xFF, x & 0xFF
98
101
 
99
102
 
100
- def xyz2lab(xyz: FloatSequence) -> Float3Tuple:
103
+ def xyz2lab(xyz: FloatSequence, /) -> Float3Tuple:
101
104
  x, y, z = (
102
105
  n ** (1 / 3) if n > 0.008856 else (7.787 * n) + (16 / 116)
103
106
  for n in map(truediv, xyz, (95.047, 100.0, 108.883))
@@ -108,7 +111,7 @@ def xyz2lab(xyz: FloatSequence) -> Float3Tuple:
108
111
  return L, a, b
109
112
 
110
113
 
111
- def lab2xyz(lab: FloatSequence) -> Float3Tuple:
114
+ def lab2xyz(lab: FloatSequence, /) -> Float3Tuple:
112
115
  L, a, b = lab
113
116
  y = (L + 16) / 116
114
117
  x = a / 500 + y
@@ -133,19 +136,19 @@ M_RGB2XYZ = np.array(
133
136
  M_XYZ2RGB = np.linalg.inv(M_RGB2XYZ)
134
137
 
135
138
 
136
- def rgb2xyz(rgb: RGBPixel) -> Float3Tuple:
139
+ def rgb2xyz(rgb: RGBPixel, /) -> Float3Tuple:
137
140
  x, y, z = M_RGB2XYZ @ (np.array(rgb, dtype=np.float64) / 255.0)
138
141
  return x, y, z
139
142
 
140
143
 
141
- def xyz2rgb(xyz: ShapedNDArray[tuple[Literal[3]], np.float64]) -> Int3Tuple:
144
+ def xyz2rgb(xyz: ShapedNDArray[tuple[Literal[3]], np.float64], /) -> Int3Tuple:
142
145
  r, g, b = (
143
146
  np.clip(M_XYZ2RGB @ np.array(xyz, dtype=np.float64), 0.0, 1.0) * 255.0
144
147
  ).astype(int)
145
148
  return r, g, b
146
149
 
147
150
 
148
- def hsl2rgb(hsl: FloatSequence) -> Int3Tuple:
151
+ def hsl2rgb(hsl: FloatSequence, /) -> Int3Tuple:
149
152
  h, s, L = hsl
150
153
  h /= 360
151
154
  h %= 1
@@ -174,7 +177,7 @@ def hsl2rgb(hsl: FloatSequence) -> Int3Tuple:
174
177
  return r, g, b
175
178
 
176
179
 
177
- def rgb2hsl(rgb: RGBVectorLike) -> Float3Tuple:
180
+ def rgb2hsl(rgb: RGBVectorLike, /) -> Float3Tuple:
178
181
  r, g, b = (x / 255.0 for x in rgb)
179
182
  m, _, v = sorted([r, g, b])
180
183
  L = (m + v) / 2
@@ -196,7 +199,7 @@ def rgb2hsl(rgb: RGBVectorLike) -> Float3Tuple:
196
199
  return (360 * h, s, L)
197
200
 
198
201
 
199
- def hsv2rgb(hsv: FloatSequence) -> Int3Tuple:
202
+ def hsv2rgb(hsv: FloatSequence, /) -> Int3Tuple:
200
203
  h, s, v = hsv
201
204
  c = v * s
202
205
  x = c * (1 - abs((h / 60) % 2 - 1))
@@ -220,7 +223,7 @@ def hsv2rgb(hsv: FloatSequence) -> Int3Tuple:
220
223
  return r, g, b
221
224
 
222
225
 
223
- def rgb2hsv(rgb: RGBVectorLike) -> Float3Tuple:
226
+ def rgb2hsv(rgb: RGBVectorLike, /) -> Float3Tuple:
224
227
  r, g, b = (x / 255.0 for x in rgb)
225
228
  m, v = sorted([r, g, b])[::2]
226
229
  delta = v - m
@@ -239,17 +242,17 @@ def rgb2hsv(rgb: RGBVectorLike) -> Float3Tuple:
239
242
  return h, s, v
240
243
 
241
244
 
242
- def lab2rgb(lab: FloatSequence) -> Int3Tuple:
245
+ def lab2rgb(lab: FloatSequence, /) -> Int3Tuple:
243
246
  xyz = lab2xyz(lab)
244
247
  return xyz2rgb(np.array(xyz, dtype=np.float64))
245
248
 
246
249
 
247
- def rgb2lab(rgb: RGBVectorLike) -> Float3Tuple:
250
+ def rgb2lab(rgb: RGBVectorLike, /) -> Float3Tuple:
248
251
  xyz = rgb2xyz(rgb)
249
252
  return xyz2lab(xyz)
250
253
 
251
254
 
252
- ANSI_4BIT_RGB: Final[list[Int3Tuple]] = [
255
+ ANSI_4BIT_RGB: Final[tuple[Int3Tuple, ...]] = (
253
256
  (0, 0, 0), # black
254
257
  (170, 0, 0), # red
255
258
  (0, 170, 0), # green
@@ -266,10 +269,10 @@ ANSI_4BIT_RGB: Final[list[Int3Tuple]] = [
266
269
  (255, 85, 255), # bright magenta
267
270
  (85, 255, 255), # bright cyan
268
271
  (255, 255, 255), # bright white
269
- ]
272
+ )
270
273
 
271
274
 
272
- def ansi_4bit_to_rgb(value: int):
275
+ def ansi_4bit_to_rgb(value: int, /):
273
276
  offset = 0
274
277
  if value > 37:
275
278
  if value <= 47:
@@ -304,26 +307,26 @@ def _4b_lookup() -> dict[Int3Tuple, Int3Tuple]:
304
307
  return table
305
308
 
306
309
 
307
- ANSI_4BIT_RGB_MAP = _4b_lookup()
310
+ ANSI_4BIT_RGB_MAP = mappingproxy(_4b_lookup())
308
311
 
309
312
 
310
- def _quantize_rgb(rgb: RGBVectorLike):
313
+ def _quantize_rgb(rgb: RGBVectorLike, /):
311
314
  r, g, b = rgb
312
315
  return min(r >> 3, 0x1F), min(g >> 3, 0x1F), min(b >> 3, 0x1F)
313
316
 
314
317
 
315
- def nearest_ansi_4bit_rgb(value: RGBVectorLike) -> Int3Tuple:
318
+ def nearest_ansi_4bit_rgb(value: RGBVectorLike, /) -> Int3Tuple:
316
319
  return ANSI_4BIT_RGB_MAP[_quantize_rgb(value)]
317
320
 
318
321
 
319
- def nearest_ansi_8bit_rgb(value: RGBVectorLike) -> Int3Tuple:
322
+ def nearest_ansi_8bit_rgb(value: RGBVectorLike, /) -> Int3Tuple:
320
323
  try:
321
324
  return ansi_8bit_to_rgb(rgb_to_ansi_8bit(value))
322
325
  except ValueError:
323
326
  raise ValueError(f"invalid RGB value: {value!r}") from None
324
327
 
325
328
 
326
- def ansi_8bit_to_rgb(value: int):
329
+ def ansi_8bit_to_rgb(value: int, /):
327
330
  if 0 <= value < 16:
328
331
  return ANSI_4BIT_RGB[value]
329
332
  elif value < 232:
@@ -335,7 +338,7 @@ def ansi_8bit_to_rgb(value: int):
335
338
  raise ValueError(f"expected an unsigned 8-bit integer, got {value}")
336
339
 
337
340
 
338
- def rgb_to_ansi_8bit(rgb: RGBVectorLike) -> int:
341
+ def rgb_to_ansi_8bit(rgb: RGBVectorLike, /) -> int:
339
342
  if len(set(rgb)) == 1:
340
343
  c = rgb[0]
341
344
  if c < 8:
@@ -347,6 +350,6 @@ def rgb_to_ansi_8bit(rgb: RGBVectorLike) -> int:
347
350
  return 16 + (36 * r) + (6 * g) + b
348
351
 
349
352
 
350
- def rgb_diff(rgb1: Int3Tuple, rgb2: Int3Tuple) -> Int3Tuple:
353
+ def rgb_diff(rgb1: Int3Tuple, rgb2: Int3Tuple, /) -> Int3Tuple:
351
354
  lab1, lab2 = map(rgb2lab, (rgb1, rgb2))
352
355
  return lab2rgb([(i + j) / 2 for i, j in zip(lab1, lab2)])