1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
#!/usr/bin/env python3
"""Regenerate logo.ans (the block-art splash) from logo.png.
logo.ans is the fallback for terminals that can't draw an image, so it has to be
rendered from the same artwork or the two drift apart -- which is exactly what
happened when logo.png was replaced and the old block art kept a scan line that
no longer existed.
Each cell is a lower half-block: the background colour paints the upper pixel,
the foreground the lower one, so one cell carries two pixel rows. Transparent
pixels emit no colour at all, letting the terminal background show through, and
a cell whose lower pixel is transparent uses an upper half-block instead so the
one opaque pixel still lands on the correct half.
Needs Pillow, so run it with a python that has it (NOT ~/ida-venv):
/usr/bin/python3 tools/make_logo_ans.py [--cols 60] [-o logo.ans]
"""
from __future__ import annotations
import argparse
import os
import sys
from PIL import Image
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ALPHA_ON = 128 # at/above this a pixel counts as present
UPPER, LOWER = "\u2580", "\u2584" # upper half block, lower half block
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--png", default=os.path.join(REPO, "logo.png"))
ap.add_argument("-o", "--out", default=os.path.join(REPO, "logo.ans"))
ap.add_argument("--cols", type=int, default=60)
ap.add_argument("--cell", default="9x22",
help="terminal cell size WxH in px, for the aspect ratio")
args = ap.parse_args()
cw, ch = (int(v) for v in args.cell.lower().split("x"))
im = Image.open(args.png).convert("RGBA")
bbox = im.getchannel("A").getbbox()
if bbox:
im = im.crop(bbox)
w, h = im.size
cols = args.cols
# two pixel rows per cell, and cells are taller than they are wide
rows = max(1, round((h / w) * cols * cw / ch))
im = im.resize((cols, rows * 2), Image.LANCZOS)
px = im.load()
out: list[str] = []
for r in range(rows):
line = []
prev = None
for c in range(cols):
top = px[c, r * 2]
bot = px[c, r * 2 + 1]
t_on, b_on = top[3] >= ALPHA_ON, bot[3] >= ALPHA_ON
if not t_on and not b_on:
sgr, ch_ = "\033[0m", " "
elif t_on and b_on:
sgr = (f"\033[38;2;{bot[0]};{bot[1]};{bot[2]}m"
f"\033[48;2;{top[0]};{top[1]};{top[2]}m")
ch_ = LOWER
elif b_on: # only the lower pixel is present
sgr = f"\033[0m\033[38;2;{bot[0]};{bot[1]};{bot[2]}m"
ch_ = LOWER
else: # only the upper pixel is present
sgr = f"\033[0m\033[38;2;{top[0]};{top[1]};{top[2]}m"
ch_ = UPPER
if sgr != prev:
line.append(sgr)
prev = sgr
line.append(ch_)
line.append("\033[0m")
out.append("".join(line))
text = "\n".join(out) + "\n"
with open(args.out, "w", encoding="utf-8") as f:
f.write(text)
print(f"{args.png} {w}x{h} -> {args.out} {cols}x{rows} cells "
f"({len(text):,} bytes, cell {cw}x{ch})")
return 0
if __name__ == "__main__":
sys.exit(main())
|