chromatic-python 0.9.0__py3-none-any.whl → 0.9.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__.pyi CHANGED
@@ -3,6 +3,7 @@ __all__ = [
3
3
  "Color",
4
4
  "ColorNamespace",
5
5
  "ColorStr",
6
+ "DEFAULT_FONT",
6
7
  "Fore",
7
8
  "SgrFlag",
8
9
  "SgrParameter",
@@ -38,10 +39,10 @@ __all__ = [
38
39
  "reshape_ansi",
39
40
  "rgb2hexstr",
40
41
  "rgb2int",
42
+ "set_default_ansi",
41
43
  "sort_glyphs",
42
44
  "ttf_extract_codepoints",
43
45
  "userfonts",
44
- "set_default_ansi",
45
46
  ]
46
47
  from . import color, data, image
47
48
  from ._version import version as __version__
@@ -66,7 +67,7 @@ from .color import (
66
67
  rgb2int,
67
68
  set_default_ansi,
68
69
  )
69
- from .data import UserFont, register_userfont, userfonts
70
+ from .data import DEFAULT_FONT, UserFont, register_userfont, userfonts
70
71
  from .image import (
71
72
  ansi2img,
72
73
  ansi_quantize,
chromatic/__main__.py CHANGED
@@ -581,7 +581,8 @@ def Parser(prog=None):
581
581
 
582
582
 
583
583
  def parse_args(argv=None):
584
- return Parser(os.path.basename((argv or sys.argv)[0])).parse_args(argv)
584
+ argv = argv or sys.argv
585
+ return Parser(os.path.basename(argv[0])).parse_args(argv[1:])
585
586
 
586
587
 
587
588
  def _call_from_ns[R](f: abc.Callable[..., R], /, ns, **kwargs) -> R:
@@ -655,10 +656,11 @@ def handle_image(ns):
655
656
  params = {}
656
657
  if is_anim := arr.ndim == 3:
657
658
  params.update(save_all=True)
659
+ isatty = False
658
660
  if hasattr(ns, "dumpfile"):
659
661
  from .color.core import color_chain
660
662
 
661
- if is_anim and ns.dumpfile.isatty():
663
+ if (isatty := ns.dumpfile.isatty()) and is_anim:
662
664
 
663
665
  def anim_loop(n: int | None, duration: int | float):
664
666
  from time import sleep
@@ -691,6 +693,11 @@ def handle_image(ns):
691
693
  ns.dumpfile.write(f"{cc}\x1b[0m\n".encode())
692
694
  if img is None:
693
695
  return
696
+ if not hasattr(ns, "outfile_callback"):
697
+ show = getattr(ns, "show", None)
698
+ if show is True or not (show is False or isatty):
699
+ return img.show()
700
+ return
694
701
  try:
695
702
  outpath = ns.outfile_callback(ns, img, **params)
696
703
  except Exception as e:
chromatic/_version.py CHANGED
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.9.0'
22
- __version_tuple__ = version_tuple = (0, 9, 0)
21
+ __version__ = version = '0.9.2'
22
+ __version_tuple__ = version_tuple = (0, 9, 2)
23
23
 
24
- __commit_id__ = commit_id = 'g17e936a21'
24
+ __commit_id__ = commit_id = 'gbe4e6a5cd'
chromatic/color/core.py CHANGED
@@ -3,6 +3,7 @@ __all__ = [
3
3
  "Color",
4
4
  "ColorChainDType",
5
5
  "ColorStr",
6
+ "DEFAULT_ANSI",
6
7
  "SGR_RESET",
7
8
  "SgrFlag",
8
9
  "SgrParameter",
@@ -16,6 +17,7 @@ __all__ = [
16
17
  "is_vt_enabled",
17
18
  "randcolor",
18
19
  "rgb2ansi_escape",
20
+ "set_default_ansi",
19
21
  ]
20
22
 
21
23
  import collections.abc as abc
@@ -1349,17 +1351,10 @@ class ColorStr(str, _IntFloatMixin):
1349
1351
  return self._weak_var_update(sgr=sgr)
1350
1352
 
1351
1353
  def strip_style(self):
1352
- only_colors = []
1353
- diff = False
1354
- for x in self._sgr:
1355
- if x.is_color():
1356
- only_colors.append(x)
1357
- elif not diff:
1358
- diff = True
1359
- if not diff:
1354
+ if all(x.is_color() for x in self._sgr):
1360
1355
  return self
1361
- sgr = self._sgr.copy()
1362
- sgr[:] = only_colors
1356
+ sgr = SgrSequence()
1357
+ sgr.set_colors(self._sgr.rgb_dict, self.ansi_type)
1363
1358
  return self._weak_var_update(sgr=sgr)
1364
1359
 
1365
1360
  def add_reset(self):
chromatic/color/core.pyi CHANGED
@@ -3,6 +3,7 @@ __all__ = [
3
3
  "Color",
4
4
  "ColorChainDType",
5
5
  "ColorStr",
6
+ "DEFAULT_ANSI",
6
7
  "SGR_RESET",
7
8
  "SgrFlag",
8
9
  "SgrParameter",
@@ -16,6 +17,7 @@ __all__ = [
16
17
  "is_vt_enabled",
17
18
  "randcolor",
18
19
  "rgb2ansi_escape",
20
+ "set_default_ansi",
19
21
  ]
20
22
 
21
23
  import collections.abc as abc
@@ -169,8 +171,6 @@ class SgrFlag(enum.IntFlag):
169
171
  @property
170
172
  def parameters(self) -> list[SgrParameter]: ...
171
173
 
172
- _P2F: tp.Final[dict[int, int]]
173
- _F2P: tp.Final[dict[int, int]]
174
174
  _ANSI16C_I2KV: tp.Final[dict[int, tuple[ColorDictKeys, Int3Tuple]]]
175
175
  _ANSI16C_KV2I: tp.Final[dict[tuple[ColorDictKeys, Int3Tuple], int]]
176
176
  _ANSI256_B2KEY: tp.Final[dict[L[b"38", b"48"], ColorDictKeys]]
@@ -29,17 +29,9 @@ class UserFont:
29
29
  def __fspath__(self) -> str: ...
30
30
  def to_truetype(self) -> FreeTypeFont: ...
31
31
 
32
- class _TypedDictStruct(tp.NamedTuple):
33
- required: frozenset[str]
34
- optional: frozenset[str]
35
- all_keys: frozenset[str]
36
- annotations: MappingProxyType[str, type[tp.Any]]
37
-
38
- @classmethod
39
- def from_typeddict(cls, typ: type[tp.TypedDict], /) -> tp.Self: ...
40
- def match(self, obj: dict, /) -> bool: ...
41
-
42
- def _userfont_dict_struct() -> _TypedDictStruct: ...
32
+ def _userfont_dict_struct() -> (
33
+ tuple[frozenset[str], frozenset[str], frozenset[str], MappingProxyType[str, tp.Any]]
34
+ ): ...
43
35
 
44
36
  userfonts: tp.Final[MappingProxyType[str, UserFont]]
45
37
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chromatic-python
3
- Version: 0.9.0
3
+ Version: 0.9.2
4
4
  Summary: ANSI art image processing and colored terminal text
5
5
  Author: crypt0lith
6
6
  License-Expression: MIT
@@ -31,6 +31,7 @@ Dynamic: license-file
31
31
  ![image](https://img.shields.io/pypi/pyversions/chromatic-python)
32
32
  [![image](https://static.pepy.tech/badge/chromatic-python)](https://pepy.tech/projects/chromatic-python)
33
33
  [![image](https://mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
34
+ [![CodSpeed](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://app.codspeed.io/crypt0lith/chromatic?utm_source=badge)
34
35
 
35
36
  Chromatic is a library for ANSI art image processing and colored terminal text.
36
37
 
@@ -45,127 +46,127 @@ It offers a collection of algorithms and types for a variety of use cases:
45
46
 
46
47
  ### Usage
47
48
 
48
- #### `color_chain`
49
+ #### `ColorStr`
50
+
51
+ ```python
52
+ from chromatic import ColorStr
53
+
54
+ cs = ColorStr("hello world", fg=0xFF0000, ansi_type="8b")
55
+
56
+ assert cs.base_str == "hello world"
57
+ assert len(cs) == len("hello world")
58
+
59
+ head, tail = cs.split()
60
+
61
+ assert head.fg == tail.fg == cs.fg
62
+ assert all(c.fg == cs.fg for c in cs)
63
+ ```
64
+
65
+ `ColorStr` is a `str` subclass that carries SGR state as metadata.
66
+ Its `str` methods delegate to the base string and return `ColorStr` instances that retain the SGR state.
67
+
68
+ ```python
69
+ assert str(cs) == "\x1b[38;5;196mhello world\x1b[0m"
70
+ assert cs.ansi == b"\x1b[38;5;196m"
71
+ ```
72
+
73
+ The SGR sequence materializes when the `ColorStr` is converted back to a Python `str`.
49
74
 
50
- A `color_chain` is a printable sequence of colored text that round-trips with a NumPy array:
75
+ #### `color_chain`
51
76
 
52
77
  ```python
53
- from chromatic import color_chain, ColorStr
78
+ from chromatic import color_chain
54
79
 
55
80
  cc = color_chain(
56
81
  [ColorStr("hello", fg=0xFF0000), ColorStr(" world", fg=0x00FF00)], ansi_type="8b"
57
82
  )
58
83
 
59
- # parsed from a raw SGR string — no ansi_type needed
84
+ sgr, s = cc[0]
85
+
86
+ assert sgr.fg == (0xFF, 0, 0)
87
+ assert s == "hello"
88
+ ```
89
+
90
+ A `color_chain` is a sequence of colored-text fragments that round-trips with a structured NumPy array.
91
+ Each fragment is an `(sgr, text)` pair of the SGR state and the run of text it applies to.
92
+
93
+ ```python
60
94
  cc2 = color_chain("\x1b[38;5;196mhello\x1b[38;5;46m world")
61
95
 
62
96
  assert cc == cc2
97
+ ```
63
98
 
64
- # red "hello", green " world"
65
- print(cc)
99
+ The constructor also parses a raw SGR string.
100
+ It infers the color depth from the sequence, so `cc2` needs no `ansi_type`.
66
101
 
67
- # a structured ndarray of dtype [('char', '<U1'), ('sgr', '<u8'), ('rgb', 'u1', (2, 4))]
102
+ ```python
68
103
  arr = cc.term_array()
69
104
 
105
+ assert arr.dtype == [("char", "<U1"), ("sgr", "<u8"), ("rgb", "u1", (2, 4))]
70
106
  assert str(color_chain.fromarray(arr)) == str(cc)
71
107
  ```
72
108
 
73
- #### `img2ansi`
109
+ `term_array` exposes the structured `ndarray` behind the chain, and `fromarray` reverses it.
110
+ `fromarray` accepts any `ndarray` of that dtype, so an array built by other means converts to a printable `color_chain`.
74
111
 
75
- `img2ansi` reads an image and returns a `color_chain`:
112
+ #### `img2ansi`
76
113
 
77
114
  ```python
78
115
  from chromatic import img2ansi
79
- from chromatic.data import userfonts
80
-
81
- font = userfonts["vga437"]
82
-
83
- # the image, as ANSI art
84
- cc = img2ansi("input.png", font, factor=200)
85
- print(cc)
86
116
 
87
- # or the raw ndarray
88
- arr = img2ansi("input.png", font, factor=200, outarray=True)
117
+ cc = img2ansi("input.png")
89
118
  ```
90
119
 
91
- #### `ansi2img`
92
-
93
- `ansi2img` renders a `color_chain` (or array) back to an image:
120
+ `img2ansi` reads an image and returns a `color_chain`.
94
121
 
95
122
  ```python
96
- from chromatic import img2ansi, ansi2img
97
- from chromatic.data import userfonts
98
-
99
- font = userfonts["vga437"]
100
-
101
- cc = img2ansi("input.png", font, factor=200)
123
+ arr = img2ansi("input.png", outarray=True)
102
124
 
103
- # a PIL.Image.Image
104
- img = ansi2img(cc, font, font_size=16)
105
- img.show()
125
+ assert color_chain.fromarray(arr) == cc
106
126
  ```
107
127
 
108
- #### `ansify`
128
+ `outarray=True` returns the underlying `ndarray` instead.
109
129
 
110
- `ansify` runs both steps at once, returning the rendered image with its array on `Image.info`:
130
+ #### `ansi2img`
111
131
 
112
132
  ```python
113
- from chromatic import ansify
114
- from chromatic.data import userfonts
133
+ from chromatic import ansi2img
115
134
 
116
- font = userfonts["vga437"]
117
-
118
- img = ansify("input.png", font, font_size=16, factor=200)
135
+ img = ansi2img(cc)
119
136
  img.show()
120
-
121
- # the ndarray it rendered from
122
- arr = img.info["ansi_array"]
123
137
  ```
124
138
 
125
- #### Animated images
139
+ `ansi2img` renders a `color_chain` back to an image.
140
+ It also accepts a dtype-matching array.
126
141
 
127
- An animated image becomes a list of `color_chain` frames, one per frame — so a GIF plays straight to the terminal:
142
+ #### `ansify`
128
143
 
129
144
  ```python
130
- import sys
131
- import time
132
-
133
- from chromatic import img2ansi
134
- from chromatic.data import userfonts
135
-
136
- font = userfonts["vga437"]
137
-
138
- # list[color_chain], one per frame
139
- frames = img2ansi("input.gif", font, factor=200)
145
+ from chromatic import ansify
140
146
 
141
- for cc in frames:
142
- # redraw each frame in place
143
- sys.stdout.write(f"\x1b[H{cc}")
144
- time.sleep(0.1)
147
+ img = ansify("input.png")
148
+ arr = img.info["ansi_array"]
145
149
  ```
146
150
 
147
- `ansify` renders the same GIF back to an animated image, and `chromatic image ansify input.gif --stdout` plays it in the terminal for you.
148
-
149
- #### `ColorStr`
151
+ `ansify` runs both directions in one call, returning the rendered image with the array it rendered from on `Image.info`.
150
152
 
151
- `ColorStr` is a `str` subclass that carries its SGR color as metadata; the escape codes only surface when you render it:
153
+ #### Animated images
152
154
 
153
155
  ```python
154
- from chromatic import ColorStr
155
-
156
- cs = ColorStr("hello world", fg=0xFF0000, ansi_type="8b")
156
+ frames = img2ansi("input.gif")
157
157
 
158
- assert isinstance(cs, str)
158
+ assert isinstance(frames, list)
159
+ assert all(isinstance(f, color_chain) for f in frames)
160
+ ```
159
161
 
160
- # the codes aren't part of the string itself
161
- assert cs.base_str == "hello world"
162
- assert len(cs) == len("hello world")
162
+ An animated image renders to a `list[color_chain]`, one per frame.
163
163
 
164
- # they surface when you print / str() it
165
- assert str(cs) == "\x1b[38;5;196mhello world\x1b[0m"
166
- assert cs.ansi == b"\x1b[38;5;196m"
164
+ ```shell
165
+ chromatic image ansify input.gif --stdout
167
166
  ```
168
167
 
168
+ The [CLI](https://crypt0lith.github.io/chromatic/cli/image/#chromatic-image-ansify) renders and plays one straight to the terminal.
169
+
169
170
  ### Installation
170
171
 
171
172
  Install the package using `pip`:
@@ -1,19 +1,19 @@
1
1
  chromatic/__init__.py,sha256=OMvWLvDuaYpXaku9n0yKa0TShBw-nmY_YuC5MsSxyK4,200
2
- chromatic/__init__.pyi,sha256=s0CDzmeUneeuctXnlgyJgpr8lIEfBEy4SfEtLJqzoIM,1632
3
- chromatic/__main__.py,sha256=a69MO7mCe4ZBQ6eH2IS7IVAFcRsTRrCD7_sKS0zQyAI,27828
2
+ chromatic/__init__.pyi,sha256=ag2uIy4vaFiWQbfjaGMv00H1txh_XnzqeAfMien7aF8,1666
3
+ chromatic/__main__.py,sha256=nm3FcPuGOz2p9cFiNp5IOvmtKTnAggBYRGfr9iwe5Ns,28065
4
4
  chromatic/_typing.py,sha256=sCBi3aJrgm9efUL22jZg6S4fWSQj39zLx56ZrfAAjvU,3695
5
- chromatic/_version.py,sha256=y3F7WNTkf-nsItn1oEzXiDxiPYz2455ljM1BhMqIjc0,528
5
+ chromatic/_version.py,sha256=z01EEho55rtFSmtUIY_dFYiPC8OnFS0OJuwAOg4f-as,528
6
6
  chromatic/color/__init__.py,sha256=t19DW9WMdd6FEN69vkckVRlsDPBHldulHv_JulwXxFc,99
7
7
  chromatic/color/__init__.pyi,sha256=38k9rRtewx5jAG9MgHr77bvPVLEg3ULf8CkJfipnNZc,1774
8
8
  chromatic/color/colorconv.py,sha256=WTgRYeFxau0A7S10S7Zi4Y7Dg-Rcy-I6Sgm3LjTMPA4,11342
9
9
  chromatic/color/colorconv.pyi,sha256=uSw1bC2KTKugY-eSlnAfE40h22zqiDGYf6yXtaAGcKE,7669
10
- chromatic/color/core.py,sha256=xhhPHZ-h1SC_Hhi-Tm--Gmx84LmJEAC9K8eVB_f9FaM,68623
11
- chromatic/color/core.pyi,sha256=5zuZz5LlF40p6KrEtRfpLRJEGvoHbQ1mL3UPyE3kbGE,20666
10
+ chromatic/color/core.py,sha256=xNnsev0Vgj80VDE6i0ViYLQFse-7ToD4-ata_N2bjQY,68526
11
+ chromatic/color/core.pyi,sha256=QHGe6WE8FKkgwoavi_OakUl-cHF5uAobDxO-XZp5woU,20648
12
12
  chromatic/color/palette.py,sha256=AMKRPUkq9BRD9iCalQ0RNlvcAF7onpwfN2DWQ2B8VhE,22850
13
13
  chromatic/color/palette.pyi,sha256=zqcuBYPACoYSJOZf_QynX3DWvHi9GJkdZklwby0mzS0,9572
14
14
  chromatic/data/__init__.py,sha256=-TKr_ALaWQ_WPyIF9v-W3aXFgWe1_-95A8jthIK3yy4,406
15
15
  chromatic/data/userfont.py,sha256=Acy2xl7YTd2r5CvbQ3lCqPPyv9xoD_Uy-OcZIjZy6dQ,13973
16
- chromatic/data/userfont.pyi,sha256=ktSAAIMFYiAJ50imANtPn4Osa4OYRTSb8BcaMssB__w,2110
16
+ chromatic/data/userfont.pyi,sha256=yIW8ANTAIbSw4kZv3UBRnEE7_MU6plG-nXMdm8d2RKk,1869
17
17
  chromatic/data/fonts/userfont.schema.json,sha256=BwJtyoAduv1AL8G4g22OsImzXMuQZXv3zoBXbPl40pM,625
18
18
  chromatic/data/fonts/vga437.ttf,sha256=qMdn-pJWJNKNmHnDoDqGIE94vOTezaCiBv0VK92QbJQ,50124
19
19
  chromatic/image/__init__.py,sha256=t19DW9WMdd6FEN69vkckVRlsDPBHldulHv_JulwXxFc,99
@@ -21,9 +21,9 @@ chromatic/image/__init__.pyi,sha256=IjC_HdMMTWznSMA6UprBsvEV8BB65PxBlYtrfDJudd0,
21
21
  chromatic/image/_array.py,sha256=6_eZOza5D_rLW8-uyyyD-TDZQQSSho70ec6mJp5SpQA,40493
22
22
  chromatic/image/_curses.py,sha256=GAlUvLfsXFDmyGg8Di_TKNfyHZnTf7NTAKZW6o7QHTc,1601
23
23
  chromatic/image/_glyph.py,sha256=RfiVwwzA3jxjObpOjV-zj2aipSndu1TrOOce6K7c2Eo,2534
24
- chromatic_python-0.9.0.dist-info/licenses/LICENSE,sha256=fKQvJY6-i5UcR_yGbIv2W7oAehJMTJY0NvRtWoXwHKE,1066
25
- chromatic_python-0.9.0.dist-info/METADATA,sha256=lLiwWvAaEkhJ6YZ1bYpEWiVWaVUBygD7F58tz6aZpZw,4950
26
- chromatic_python-0.9.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
27
- chromatic_python-0.9.0.dist-info/entry_points.txt,sha256=Fk_pV70Sz340dSNfzHu-JycTf2nO0Fa60LkANe73_YA,54
28
- chromatic_python-0.9.0.dist-info/top_level.txt,sha256=wjzxcxfjO8I4u22BS8wL67Bq64c59kbZCqQ--Fc_Mqw,10
29
- chromatic_python-0.9.0.dist-info/RECORD,,
24
+ chromatic_python-0.9.2.dist-info/licenses/LICENSE,sha256=fKQvJY6-i5UcR_yGbIv2W7oAehJMTJY0NvRtWoXwHKE,1066
25
+ chromatic_python-0.9.2.dist-info/METADATA,sha256=Co4HNqjShFi34TgNu1ogHOyUuT-GY5-B9WeKOLNFt9I,5166
26
+ chromatic_python-0.9.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
27
+ chromatic_python-0.9.2.dist-info/entry_points.txt,sha256=Fk_pV70Sz340dSNfzHu-JycTf2nO0Fa60LkANe73_YA,54
28
+ chromatic_python-0.9.2.dist-info/top_level.txt,sha256=wjzxcxfjO8I4u22BS8wL67Bq64c59kbZCqQ--Fc_Mqw,10
29
+ chromatic_python-0.9.2.dist-info/RECORD,,