chromatic-python 0.7.2__tar.gz → 0.7.3__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.
- chromatic_python-0.7.3/.github/workflows/docs.yml +36 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/PKG-INFO +5 -4
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/__init__.pyi +2 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/__main__.py +162 -110
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/_version.py +3 -3
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/color/__init__.pyi +12 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/color/colorconv.py +66 -25
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/color/colorconv.pyi +61 -2
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/color/core.py +233 -192
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/color/core.pyi +8 -3
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/color/palette.py +56 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/image/__init__.pyi +0 -12
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/image/_array.py +2 -2
- chromatic_python-0.7.3/chromatic/image/_curses.py +41 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic_python.egg-info/PKG-INFO +5 -4
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic_python.egg-info/SOURCES.txt +7 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic_python.egg-info/requires.txt +2 -2
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic_python.egg-info/scm_file_list.json +31 -24
- chromatic_python-0.7.3/chromatic_python.egg-info/scm_version.json +8 -0
- chromatic_python-0.7.3/docs/api/chromatic.md +3 -0
- chromatic_python-0.7.3/docs/api/color.md +1 -0
- chromatic_python-0.7.3/docs/api/data.md +1 -0
- chromatic_python-0.7.3/docs/api/image.md +1 -0
- chromatic_python-0.7.3/docs/index.md +134 -0
- chromatic_python-0.7.3/mkdocs.yml +34 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/pyproject.toml +11 -3
- chromatic_python-0.7.2/chromatic/image/_curses.py +0 -136
- chromatic_python-0.7.2/chromatic_python.egg-info/scm_version.json +0 -8
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/.gitattributes +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/.github/dependabot.yml +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/.github/workflows/release.yml +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/.gitignore +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/README.md +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/banner.png +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/__init__.py +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/_typing.py +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/color/__init__.py +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/color/palette.pyi +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/data/__init__.py +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/data/__init__.pyi +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/data/_fetchers.py +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/data/fonts/userfont.schema.json +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/data/fonts/vga437.ttf +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/data/images/butterfly.jpg +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/data/images/escher.png +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/data/images/goblin_virus.png +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/data/images/registry.json +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/data/userfont.py +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/data/userfont.pyi +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/demo.py +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/image/__init__.py +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/image/_glyph.py +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic_python.egg-info/dependency_links.txt +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic_python.egg-info/entry_points.txt +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic_python.egg-info/top_level.txt +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/setup.cfg +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/tests/__init__.py +0 -0
- {chromatic_python-0.7.2 → chromatic_python-0.7.3}/tests/test_color_str.py +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [released]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
pages: write
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: pages
|
|
14
|
+
cancel-in-progress: false
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
build:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- uses: astral-sh/setup-uv@v5
|
|
22
|
+
- run: uv sync --group docs
|
|
23
|
+
- run: uv run mkdocs build
|
|
24
|
+
- uses: actions/upload-pages-artifact@v3
|
|
25
|
+
with:
|
|
26
|
+
path: site
|
|
27
|
+
|
|
28
|
+
deploy:
|
|
29
|
+
needs: build
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
environment:
|
|
32
|
+
name: github-pages
|
|
33
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
34
|
+
steps:
|
|
35
|
+
- id: deployment
|
|
36
|
+
uses: actions/deploy-pages@v4
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chromatic-python
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.3
|
|
4
4
|
Summary: ANSI art image processing and colored terminal text
|
|
5
5
|
Author: crypt0lith
|
|
6
6
|
License-Expression: MIT
|
|
7
|
-
Project-URL:
|
|
7
|
+
Project-URL: Homepage, https://github.com/crypt0lith/chromatic
|
|
8
|
+
Project-URL: Documentation, https://crypt0lith.github.io/chromatic
|
|
8
9
|
Keywords: ansi,ascii,art,font,image,terminal,parser
|
|
9
10
|
Classifier: Programming Language :: Python :: 3
|
|
10
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -15,9 +16,9 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
15
16
|
Classifier: Typing :: Typed
|
|
16
17
|
Requires-Python: <4.0,>=3.12
|
|
17
18
|
Description-Content-Type: text/markdown
|
|
18
|
-
Requires-Dist: numpy<2.
|
|
19
|
+
Requires-Dist: numpy<2.8,>=2.1
|
|
19
20
|
Requires-Dist: pillow>=12.2.0
|
|
20
|
-
Requires-Dist: scipy
|
|
21
|
+
Requires-Dist: scipy>=1.11.4
|
|
21
22
|
Requires-Dist: opencv-python
|
|
22
23
|
Requires-Dist: fonttools>=4.60.2
|
|
23
24
|
Requires-Dist: lazy_loader
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# vim: foldmethod=marker foldenable
|
|
1
2
|
import collections.abc as abc
|
|
2
3
|
import os
|
|
3
4
|
import sys
|
|
@@ -9,6 +10,8 @@ from inspect import signature
|
|
|
9
10
|
def parse_args():
|
|
10
11
|
import argparse as ap
|
|
11
12
|
|
|
13
|
+
from . import __version__
|
|
14
|
+
|
|
12
15
|
class SetEnvAction(ap.Action):
|
|
13
16
|
def __init__(self, *args, **kwargs):
|
|
14
17
|
if kwargs.get("nargs") not in ("?", None):
|
|
@@ -37,7 +40,7 @@ def parse_args():
|
|
|
37
40
|
out = super()._get_help_string(action)
|
|
38
41
|
if out is not None and isinstance(action, SetEnvAction):
|
|
39
42
|
out += " (env: {}={})".format(
|
|
40
|
-
|
|
43
|
+
action.env, os.environ.get(action.env, "")
|
|
41
44
|
)
|
|
42
45
|
return out
|
|
43
46
|
|
|
@@ -63,21 +66,57 @@ def parse_args():
|
|
|
63
66
|
|
|
64
67
|
font_dir_env_base = init_font_dir_env_parser()
|
|
65
68
|
|
|
66
|
-
|
|
67
|
-
top_parser = ap.ArgumentParser(prog=__package__)
|
|
68
|
-
else:
|
|
69
|
-
top_parser = ap.ArgumentParser()
|
|
69
|
+
from .color.palette import ColorNamespace
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
color_ns = ColorNamespace.asdict()
|
|
72
|
+
|
|
73
|
+
def ColorArgType(allow_rgba=False):
|
|
74
|
+
def color_arg(s: str, /) -> tuple[int, int, int, int] | tuple[int, int, int]:
|
|
75
|
+
try:
|
|
76
|
+
return color_ns[s.strip().replace(" ", "_").upper()]
|
|
77
|
+
except KeyError:
|
|
78
|
+
if any(c not in "0123456789abcdef" for c in s.casefold()):
|
|
79
|
+
raise
|
|
80
|
+
n = len(s)
|
|
81
|
+
if allow_rgba and n % 4 == 0:
|
|
82
|
+
if n == 4:
|
|
83
|
+
s = "".join(c * 2 for c in s)
|
|
84
|
+
x = int(s, 16)
|
|
85
|
+
if not 0 <= x < (1 << 32):
|
|
86
|
+
raise ValueError(f"{x:#x} is not u32")
|
|
87
|
+
return (
|
|
88
|
+
(x >> 24) & 0xFF,
|
|
89
|
+
(x >> 16) & 0xFF,
|
|
90
|
+
(x >> 8) & 0xFF,
|
|
91
|
+
x & 0xFF,
|
|
92
|
+
) # fmt: skip
|
|
93
|
+
elif n % 3 == 0:
|
|
94
|
+
if n == 3:
|
|
95
|
+
s = "".join(c * 2 for c in s)
|
|
96
|
+
x = int(s, 16)
|
|
97
|
+
if not 0 <= x < (1 << 24):
|
|
98
|
+
raise ValueError(f"{x:#x} is not u24")
|
|
99
|
+
return (x >> 16) & 0xFF, (x >> 8) & 0xFF, x & 0xFF
|
|
100
|
+
raise ValueError(
|
|
101
|
+
"expected hex digit with nbytes in {}, got {}".format(
|
|
102
|
+
(3, 4, 6, 8), int(s, 16).bit_length() // 8
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return color_arg
|
|
72
107
|
|
|
73
108
|
def init_font_subcmds(parser: ap.ArgumentParser):
|
|
74
109
|
subcmds = parser.add_subparsers(dest="subcmd", required=True)
|
|
110
|
+
|
|
75
111
|
mut_by_name_base = new_base_parser()
|
|
76
112
|
mut_by_name_base.add_argument(
|
|
77
113
|
dest="name", metavar="NAME", help="name of the registered font"
|
|
78
114
|
)
|
|
115
|
+
|
|
79
116
|
uf_attr_base = new_base_parser()
|
|
117
|
+
|
|
80
118
|
uf_attr_opts = uf_attr_base.add_argument_group("font attribute options")
|
|
119
|
+
# font attribute options {{{
|
|
81
120
|
uf_attr_opts.add_argument(
|
|
82
121
|
"--size", dest="size", type=int, help="size in pixels"
|
|
83
122
|
)
|
|
@@ -96,6 +135,9 @@ def parse_args():
|
|
|
96
135
|
action=ap.BooleanOptionalAction,
|
|
97
136
|
help="whether to set this font as the new default font",
|
|
98
137
|
)
|
|
138
|
+
# }}}
|
|
139
|
+
|
|
140
|
+
# subcommands: font {{{
|
|
99
141
|
subcmds.add_parser(
|
|
100
142
|
"delete",
|
|
101
143
|
parents=[font_dir_env_base, mut_by_name_base],
|
|
@@ -134,12 +176,18 @@ def parse_args():
|
|
|
134
176
|
set a user font as the default font.
|
|
135
177
|
it will be accessible as `{__package__}.DEFAULT_FONT`""",
|
|
136
178
|
)
|
|
179
|
+
# }}}
|
|
180
|
+
|
|
181
|
+
# font edit {{{
|
|
137
182
|
p_edit_font_opts = subcmd_p_edit.add_argument_group(
|
|
138
183
|
"font options", argument_default=ap.SUPPRESS
|
|
139
184
|
)
|
|
140
185
|
p_edit_font_opts.add_argument(
|
|
141
186
|
"--font", dest="font", metavar="FILE", help="path to truetype font file"
|
|
142
187
|
)
|
|
188
|
+
# }}}
|
|
189
|
+
|
|
190
|
+
# font list {{{
|
|
143
191
|
p_list_fmt_opts = subcmd_p_list.add_argument_group("formatting options")
|
|
144
192
|
p_list_fmt_opts.add_argument(
|
|
145
193
|
"--json",
|
|
@@ -147,6 +195,9 @@ def parse_args():
|
|
|
147
195
|
action="store_true",
|
|
148
196
|
help="emit entries in JSON format",
|
|
149
197
|
)
|
|
198
|
+
# }}}
|
|
199
|
+
|
|
200
|
+
# font register {{{
|
|
150
201
|
subcmd_p_register.add_argument(
|
|
151
202
|
dest="font", metavar="FONT", help="path to truetype font file"
|
|
152
203
|
)
|
|
@@ -167,16 +218,77 @@ def parse_args():
|
|
|
167
218
|
create a symlink instead of copying the file.
|
|
168
219
|
like `ln -s TARGET LINK_NAME`, this will fail if the link name already exists""",
|
|
169
220
|
)
|
|
221
|
+
# }}}
|
|
222
|
+
|
|
223
|
+
# font rename {{{
|
|
170
224
|
subcmd_p_rename.add_argument(
|
|
171
225
|
dest="newname",
|
|
172
226
|
metavar="NEWNAME",
|
|
173
227
|
help="new name to give the registered font",
|
|
174
228
|
)
|
|
229
|
+
# }}}
|
|
175
230
|
|
|
176
231
|
def init_image_subcmds(parser: ap.ArgumentParser):
|
|
232
|
+
def save_img_callback(path: str | os.PathLike[str] | None = None, /):
|
|
233
|
+
|
|
234
|
+
# deferred exception handler
|
|
235
|
+
# on write error, fall-through to Image.show()
|
|
236
|
+
# then re-raise after in-memory image is opened
|
|
237
|
+
|
|
238
|
+
from functools import wraps
|
|
239
|
+
|
|
240
|
+
def defer_exc[**P, R](
|
|
241
|
+
f: abc.Callable[P, abc.Generator[Exception, tp.Any, R]], /
|
|
242
|
+
):
|
|
243
|
+
@wraps(f)
|
|
244
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
245
|
+
err = None
|
|
246
|
+
it = f(*args, **kwargs)
|
|
247
|
+
try:
|
|
248
|
+
while True:
|
|
249
|
+
err = next(it)
|
|
250
|
+
except StopIteration as stop:
|
|
251
|
+
return stop.value
|
|
252
|
+
finally:
|
|
253
|
+
if err is not None:
|
|
254
|
+
raise err
|
|
255
|
+
|
|
256
|
+
return wrapper
|
|
257
|
+
|
|
258
|
+
import PIL.Image
|
|
259
|
+
|
|
260
|
+
@defer_exc
|
|
261
|
+
def callback(ns: ap.Namespace, img: PIL.Image.Image):
|
|
262
|
+
if path is not None:
|
|
263
|
+
try:
|
|
264
|
+
img.save(path)
|
|
265
|
+
except Exception as e:
|
|
266
|
+
yield e
|
|
267
|
+
else:
|
|
268
|
+
if getattr(ns, "show", False):
|
|
269
|
+
with PIL.Image.open(path) as f:
|
|
270
|
+
f.show()
|
|
271
|
+
return path
|
|
272
|
+
dump_stdout = hasattr(ns, "dumpfile") and ns.dumpfile.fileno() == 1
|
|
273
|
+
if getattr(ns, "show", not dump_stdout):
|
|
274
|
+
img.show()
|
|
275
|
+
|
|
276
|
+
return callback
|
|
277
|
+
|
|
278
|
+
def save_img_to_dir(dirname: str, /):
|
|
279
|
+
from datetime import datetime
|
|
280
|
+
from pathlib import Path
|
|
281
|
+
|
|
282
|
+
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
|
283
|
+
outfile = Path(dirname, f"{__package__}_{timestamp}.png")
|
|
284
|
+
return save_img_callback(outfile)
|
|
285
|
+
|
|
177
286
|
subcmds = parser.add_subparsers(dest="subcmd", required=True)
|
|
287
|
+
|
|
178
288
|
ansify_base = new_base_parser()
|
|
289
|
+
|
|
179
290
|
font_opts = ansify_base.add_argument_group("font options")
|
|
291
|
+
# font options {{{
|
|
180
292
|
font_opts.add_argument(
|
|
181
293
|
"--font",
|
|
182
294
|
dest="font",
|
|
@@ -189,61 +301,22 @@ def parse_args():
|
|
|
189
301
|
metavar="SIZE",
|
|
190
302
|
help="font size, in pixels",
|
|
191
303
|
)
|
|
192
|
-
|
|
193
|
-
from .color.palette import ColorNamespace
|
|
194
|
-
|
|
195
|
-
color_ns = ColorNamespace.asdict()
|
|
196
|
-
|
|
197
|
-
def color_parser(allow_rgba=False):
|
|
198
|
-
def parsed_color(
|
|
199
|
-
s: str, /
|
|
200
|
-
) -> tuple[int, int, int, int] | tuple[int, int, int]:
|
|
201
|
-
try:
|
|
202
|
-
return color_ns[s.strip().replace(" ", "_").upper()]
|
|
203
|
-
except KeyError:
|
|
204
|
-
if any(c not in "0123456789abcdef" for c in s.casefold()):
|
|
205
|
-
raise
|
|
206
|
-
n = len(s)
|
|
207
|
-
if allow_rgba and n % 4 == 0:
|
|
208
|
-
if n == 4:
|
|
209
|
-
s = "".join(c * 2 for c in s)
|
|
210
|
-
x = int(s, 16)
|
|
211
|
-
if not 0 <= x < (1 << 32):
|
|
212
|
-
raise ValueError(f"{x:#x} is not u32")
|
|
213
|
-
return (
|
|
214
|
-
(x >> 24) & 0xFF,
|
|
215
|
-
(x >> 16) & 0xFF,
|
|
216
|
-
(x >> 8) & 0xFF,
|
|
217
|
-
x & 0xFF,
|
|
218
|
-
) # fmt: skip
|
|
219
|
-
elif n % 3 == 0:
|
|
220
|
-
if n == 3:
|
|
221
|
-
s = "".join(c * 2 for c in s)
|
|
222
|
-
x = int(s, 16)
|
|
223
|
-
if not 0 <= x < (1 << 24):
|
|
224
|
-
raise ValueError(f"{x:#x} is not u24")
|
|
225
|
-
return (x >> 16) & 0xFF, (x >> 8) & 0xFF, x & 0xFF
|
|
226
|
-
raise ValueError(
|
|
227
|
-
"expected hex digit with nbytes in {}, got {}".format(
|
|
228
|
-
(3, 4, 6, 8), int(s, 16).bit_length() // 8
|
|
229
|
-
)
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
return parsed_color
|
|
304
|
+
# }}}
|
|
233
305
|
|
|
234
306
|
color_opts = ansify_base.add_argument_group("color defaults")
|
|
307
|
+
# color defaults {{{
|
|
235
308
|
color_opts.add_argument(
|
|
236
309
|
"--bg",
|
|
237
310
|
dest="bg_default",
|
|
238
311
|
metavar="COLOR",
|
|
239
|
-
type=
|
|
312
|
+
type=ColorArgType(allow_rgba=True),
|
|
240
313
|
help="default background color. can be hex rgb(a) value or color name",
|
|
241
314
|
)
|
|
242
315
|
color_opts.add_argument(
|
|
243
316
|
"--fg",
|
|
244
317
|
dest="fg_default",
|
|
245
318
|
metavar="COLOR",
|
|
246
|
-
type=
|
|
319
|
+
type=ColorArgType(allow_rgba=True),
|
|
247
320
|
help="default foreground color. can be hex rgb(a) value or color name",
|
|
248
321
|
)
|
|
249
322
|
color_opts.add_argument(
|
|
@@ -252,10 +325,12 @@ def parse_args():
|
|
|
252
325
|
action="store_true",
|
|
253
326
|
help="use alpha channel with defaults: bg=0, fg=0xFF",
|
|
254
327
|
)
|
|
328
|
+
# }}}
|
|
255
329
|
|
|
256
330
|
from_img_base = new_base_parser()
|
|
257
331
|
|
|
258
332
|
colorize_opts = from_img_base.add_argument_group("colorization options")
|
|
333
|
+
# colorization options {{{
|
|
259
334
|
colorize_opts.add_argument(
|
|
260
335
|
"-a",
|
|
261
336
|
"--ansi-type",
|
|
@@ -266,6 +341,7 @@ def parse_args():
|
|
|
266
341
|
since truecolor is rgb, '24b' effectively means 'original colors'
|
|
267
342
|
(choices: %(choices)s)""",
|
|
268
343
|
)
|
|
344
|
+
|
|
269
345
|
equalize_opts = colorize_opts.add_mutually_exclusive_group()
|
|
270
346
|
equalize_opts.add_argument(
|
|
271
347
|
"--equalize",
|
|
@@ -281,18 +357,22 @@ def parse_args():
|
|
|
281
357
|
const="white_point",
|
|
282
358
|
help="apply white-point equalization before image-to-ansi conversion",
|
|
283
359
|
)
|
|
360
|
+
|
|
284
361
|
colorize_opts.add_argument(
|
|
285
362
|
"--input-bg",
|
|
286
363
|
dest="bg",
|
|
287
364
|
metavar="COLOR",
|
|
288
|
-
type=
|
|
365
|
+
type=ColorArgType(),
|
|
289
366
|
default=None,
|
|
290
367
|
help="""\
|
|
291
368
|
initial background (canvas) color,
|
|
292
369
|
injected as an attribute on the intermediate text object itself.
|
|
293
370
|
can be hex rgb value or color name""",
|
|
294
371
|
)
|
|
372
|
+
# }}}
|
|
373
|
+
|
|
295
374
|
glyph_opts = from_img_base.add_argument_group("glyph options")
|
|
375
|
+
# glyph options {{{
|
|
296
376
|
glyph_opts.add_argument(
|
|
297
377
|
"--factor",
|
|
298
378
|
dest="factor",
|
|
@@ -302,7 +382,15 @@ def parse_args():
|
|
|
302
382
|
scaling factor for image width to %(metavar)s columns per output line.
|
|
303
383
|
analogous to level-of-detail""",
|
|
304
384
|
)
|
|
385
|
+
|
|
305
386
|
char_set_opts = glyph_opts.add_mutually_exclusive_group()
|
|
387
|
+
|
|
388
|
+
from .image._curses import ascii_printable, cp437_printable
|
|
389
|
+
|
|
390
|
+
# char_set {{{
|
|
391
|
+
char_set_preset_help = (
|
|
392
|
+
"use printable characters from {} character encoding as charset"
|
|
393
|
+
).format
|
|
306
394
|
char_set_opts.add_argument(
|
|
307
395
|
"-c",
|
|
308
396
|
"--chars",
|
|
@@ -312,12 +400,6 @@ def parse_args():
|
|
|
312
400
|
charset where %(metavar)s is a string containing chars to use for glyph selection.
|
|
313
401
|
a char can appear more than once, which affects frequency distribution""",
|
|
314
402
|
)
|
|
315
|
-
|
|
316
|
-
from .image._curses import ascii_printable, cp437_printable
|
|
317
|
-
|
|
318
|
-
char_set_preset_help = (
|
|
319
|
-
"use printable characters from {} character encoding as charset"
|
|
320
|
-
).format
|
|
321
403
|
char_set_opts.add_argument(
|
|
322
404
|
"--ascii",
|
|
323
405
|
dest="char_set",
|
|
@@ -350,8 +432,10 @@ def parse_args():
|
|
|
350
432
|
)
|
|
351
433
|
),
|
|
352
434
|
)
|
|
435
|
+
# }}}
|
|
353
436
|
|
|
354
437
|
glyph_sort_opts = glyph_opts.add_mutually_exclusive_group()
|
|
438
|
+
# sort_glyphs {{{
|
|
355
439
|
glyph_sort_opts.add_argument(
|
|
356
440
|
"--sort",
|
|
357
441
|
dest="sort_glyphs",
|
|
@@ -370,63 +454,13 @@ def parse_args():
|
|
|
370
454
|
sort glyphs in reverse order.
|
|
371
455
|
flips the luminance mapping to (light -> dark)""",
|
|
372
456
|
)
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
# deferred exception handler
|
|
377
|
-
# on write error, fall-through to Image.show()
|
|
378
|
-
# then re-raise after in-memory image is opened
|
|
379
|
-
|
|
380
|
-
from functools import wraps
|
|
381
|
-
|
|
382
|
-
def defer_exc[**P, R](
|
|
383
|
-
f: abc.Callable[P, abc.Generator[Exception, tp.Any, R]], /
|
|
384
|
-
):
|
|
385
|
-
@wraps(f)
|
|
386
|
-
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
387
|
-
err = None
|
|
388
|
-
it = f(*args, **kwargs)
|
|
389
|
-
try:
|
|
390
|
-
while True:
|
|
391
|
-
err = next(it)
|
|
392
|
-
except StopIteration as stop:
|
|
393
|
-
return stop.value
|
|
394
|
-
finally:
|
|
395
|
-
if err is not None:
|
|
396
|
-
raise err
|
|
397
|
-
|
|
398
|
-
return wrapper
|
|
399
|
-
|
|
400
|
-
import PIL.Image
|
|
401
|
-
|
|
402
|
-
@defer_exc
|
|
403
|
-
def callback(ns: ap.Namespace, img: PIL.Image.Image):
|
|
404
|
-
if path is not None:
|
|
405
|
-
try:
|
|
406
|
-
img.save(path)
|
|
407
|
-
except Exception as e:
|
|
408
|
-
yield e
|
|
409
|
-
else:
|
|
410
|
-
if getattr(ns, "show", False):
|
|
411
|
-
with PIL.Image.open(path) as f:
|
|
412
|
-
f.show()
|
|
413
|
-
return path
|
|
414
|
-
dump_stdout = hasattr(ns, "dumpfile") and ns.dumpfile.fileno() == 1
|
|
415
|
-
if getattr(ns, "show", not dump_stdout):
|
|
416
|
-
img.show()
|
|
417
|
-
|
|
418
|
-
return callback
|
|
419
|
-
|
|
420
|
-
def save_img_to_dir(dirname: str, /):
|
|
421
|
-
from datetime import datetime
|
|
422
|
-
from pathlib import Path
|
|
423
|
-
|
|
424
|
-
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
|
425
|
-
outfile = Path(dirname, f"{__package__}_{timestamp}.png")
|
|
426
|
-
return save_img_callback(outfile)
|
|
457
|
+
# }}}
|
|
458
|
+
# }}}
|
|
427
459
|
|
|
428
460
|
output_opts_base = new_base_parser()
|
|
461
|
+
|
|
429
462
|
output_opts = output_opts_base.add_argument_group("output options")
|
|
463
|
+
# output options {{{
|
|
430
464
|
outfile_opts = output_opts.add_mutually_exclusive_group()
|
|
431
465
|
outfile_opts.add_argument(
|
|
432
466
|
"-O",
|
|
@@ -466,14 +500,32 @@ def parse_args():
|
|
|
466
500
|
help="write the ansified image text to stdout",
|
|
467
501
|
)
|
|
468
502
|
output_opts_base.set_defaults(_outfile_callback=save_img_callback())
|
|
503
|
+
# }}}
|
|
504
|
+
|
|
505
|
+
# subcommands: image {{{
|
|
469
506
|
subcmd_p_ansify = subcmds.add_parser(
|
|
470
507
|
"ansify",
|
|
471
508
|
parents=[font_dir_env_base, ansify_base, from_img_base, output_opts_base],
|
|
472
509
|
formatter_class=EnvHelpFormatter,
|
|
473
510
|
)
|
|
511
|
+
# }}}
|
|
512
|
+
|
|
513
|
+
# image ansify {{{
|
|
474
514
|
subcmd_p_ansify.add_argument(
|
|
475
515
|
dest="img", metavar="IMAGEFILE", help="input image"
|
|
476
516
|
)
|
|
517
|
+
# }}}
|
|
518
|
+
|
|
519
|
+
if os.path.isfile(sys.argv[0]) and os.path.samefile(sys.argv[0], __file__):
|
|
520
|
+
top_parser = ap.ArgumentParser(prog=__package__)
|
|
521
|
+
else:
|
|
522
|
+
top_parser = ap.ArgumentParser()
|
|
523
|
+
|
|
524
|
+
top_parser.add_argument(
|
|
525
|
+
"-V", "--version", action="version", version=f"%(prog)s {__version__}"
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
cmd_subparsers = top_parser.add_subparsers(dest="cmd", required=True)
|
|
477
529
|
|
|
478
530
|
font_cmd_subparser = cmd_subparsers.add_parser("font")
|
|
479
531
|
image_cmd_subparser = cmd_subparsers.add_parser("image")
|
|
@@ -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.7.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 7,
|
|
21
|
+
__version__ = version = '0.7.3'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 7, 3)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g9af897cd5'
|
|
@@ -8,6 +8,7 @@ __all__ = [
|
|
|
8
8
|
"DEFAULT_ANSI",
|
|
9
9
|
"Fore",
|
|
10
10
|
"SGR_RESET",
|
|
11
|
+
"SgrFlag",
|
|
11
12
|
"SgrParameter",
|
|
12
13
|
"SgrSequence",
|
|
13
14
|
"Style",
|
|
@@ -24,8 +25,12 @@ __all__ = [
|
|
|
24
25
|
"hsv2rgb",
|
|
25
26
|
"int2rgb",
|
|
26
27
|
"is_u24",
|
|
28
|
+
"lab2lch",
|
|
27
29
|
"lab2rgb",
|
|
28
30
|
"lab2xyz",
|
|
31
|
+
"lch2lab",
|
|
32
|
+
"lch2rgb",
|
|
33
|
+
"lerp_lch",
|
|
29
34
|
"named_color",
|
|
30
35
|
"nearest_ansi_4bit_rgb",
|
|
31
36
|
"nearest_ansi_8bit_rgb",
|
|
@@ -36,6 +41,7 @@ __all__ = [
|
|
|
36
41
|
"rgb2hsv",
|
|
37
42
|
"rgb2int",
|
|
38
43
|
"rgb2lab",
|
|
44
|
+
"rgb2lch",
|
|
39
45
|
"rgb2xyz",
|
|
40
46
|
"rgb_diff",
|
|
41
47
|
"rgb_dispatch",
|
|
@@ -54,8 +60,12 @@ from .colorconv import (
|
|
|
54
60
|
hsv2rgb,
|
|
55
61
|
int2rgb,
|
|
56
62
|
is_u24,
|
|
63
|
+
lab2lch,
|
|
57
64
|
lab2rgb,
|
|
58
65
|
lab2xyz,
|
|
66
|
+
lch2lab,
|
|
67
|
+
lch2rgb,
|
|
68
|
+
lerp_lch,
|
|
59
69
|
nearest_ansi_4bit_rgb,
|
|
60
70
|
nearest_ansi_8bit_rgb,
|
|
61
71
|
rgb2hexstr,
|
|
@@ -63,6 +73,7 @@ from .colorconv import (
|
|
|
63
73
|
rgb2hsv,
|
|
64
74
|
rgb2int,
|
|
65
75
|
rgb2lab,
|
|
76
|
+
rgb2lch,
|
|
66
77
|
rgb2xyz,
|
|
67
78
|
rgb_diff,
|
|
68
79
|
rgb_to_ansi_8bit,
|
|
@@ -75,6 +86,7 @@ from .core import (
|
|
|
75
86
|
SGR_RESET,
|
|
76
87
|
Color,
|
|
77
88
|
ColorStr,
|
|
89
|
+
SgrFlag,
|
|
78
90
|
SgrParameter,
|
|
79
91
|
SgrSequence,
|
|
80
92
|
ansicolor4Bit,
|